Create: Computing AgesDoc Brass
Bibliothèques

vehicle

Où se trouve le véhicule Create Aeronautics qui porte l'ordinateur, et à quelle vitesse il va.

Micro-ordinateur, microcontrôleur, ordinateur moderne

La bibliothèque vehicle indique à un ordinateur où il se trouve dans le monde et comment il se déplace. Elle est faite pour les ordinateurs embarqués sur un véhicule Create Aeronautics (un dirigeable, un ballon, une plateforme volante) : grâce à elle, un Microcontrôleur devient compteur de vitesse, enregistreur de vol, pilote automatique ou balise qui signale la position du navire à la base.

Brass
local p = vehicle.position()
print("Position : " .. p.x .. " " .. p.y .. " " .. p.z)
print("Vitesse : " .. vehicle.speed() .. " blocs/s")

À savoir avant de commencer :

  • Quels ordinateurs. Le Micro-ordinateur, le Microcontrôleur et l'Ordinateur moderne l'ont. Sur les plus anciens, vehicle vaut nil.
  • Positions réelles. Create Aeronautics range les blocs d'un véhicule à part du monde, à des coordonnées très éloignées de l'endroit où il vole vraiment. vehicle.position() répond toujours avec la vraie place dans le monde, celle que vous lisez avec F3 en vous tenant à côté du navire. Les portées radio utilisent ces mêmes positions réelles (voir Réseaux).
  • Au sol aussi. Un ordinateur qui n'est pas sur un véhicule est son propre véhicule, à l'arrêt : vehicle.position() donne la place de l'ordinateur, vehicle.velocity() donne zéro, vehicle.name() donne nil. Sans Create Aeronautics installé, tous les ordinateurs sont dans ce cas. Une tour de contrôle peut donc se servir des mêmes fonctions que ses navires.
  • Unités et arrondis. Les positions sont en blocs, les vitesses en blocs par seconde, le tout arrondi à 0,01. Un bloc fait un mètre : des blocs par seconde fois 3,6 donnent des km/h.
  • Valeurs fraîches. Chaque appel lit le monde à cet instant : rappelez-le dans une boucle pour suivre le véhicule.
Fonctions
vehicle.position()L'endroit où se trouve l'ordinateur dans le monde : une table avec x, y et z. Le point est le centre du bloc de l'ordinateur : un ordinateur posé en 120 70 -45 (les nombres que F3 affiche pour ce bloc) répond {x = 120.5, y = 70.5, z = -44.5}.
vehicle.velocity()La vitesse de l'ordinateur le long de chaque axe, en blocs par seconde : x vers l'est, y vers le haut, z vers le sud. Les valeurs négatives vont dans l'autre sens (ouest, bas, nord). Au sol, les trois valent 0.
vehicle.speed()La vitesse de l'ordinateur, en blocs par seconde, quelle que soit la direction (la montée et la descente comptent). C'est la longueur de vehicle.velocity(). Au sol, 0.
vehicle.name()Le nom du véhicule Create Aeronautics qui porte l'ordinateur, ou nil au sol (et toujours nil sans Create Aeronautics).
vehicle.on_vehicle()true quand l'ordinateur est à bord d'un véhicule. C'est la même chose que vehicle.name() ~= nil.

Position

#

vehicle.position()

→ table⚙ coût 8

L'endroit où se trouve l'ordinateur dans le monde : une table avec x, y et z. Le point est le centre du bloc de l'ordinateur : un ordinateur posé en 120 70 -45 (les nombres que F3 affiche pour ce bloc) répond {x = 120.5, y = 70.5, z = -44.5}.

Renvoie
table
{x, y, z}, le centre du bloc de l'ordinateur dans le monde, en blocs
Brass
local p = vehicle.position()
print("X " .. p.x .. "  Y " .. p.y .. "  Z " .. p.z)

À quelle distance est la base ? La distance entre deux points, avec Pythagore (la position est ici écrite à la main, un vrai programme utilise vehicle.position()) :

Brass
local BASE = {x = 250.5, y = 72.5, z = -1199.5}

local function distance_vers(a, b)
  local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
  return math.sqrt(dx * dx + dy * dy + dz * dz)
end

local ici = {x = 130.5, y = 112.5, z = -1109.5}   -- vehicle.position() à bord
print("base à " .. math.floor(distance_vers(ici, BASE)) .. " blocs")
Écran
base à 155 blocs

Un enregistreur de vol. Un Microcontrôleur à bord écrit une ligne par seconde dans un fichier : le temps, la position et la vitesse, au format CSV qu'ouvre un tableur. Il s'arrête quand sa mémoire est presque pleine :

Brass
local FICHIER = "vol.csv"
if not fs.exists(FICHIER) then
  fs.write(FICHIER, "temps,x,y,z,vitesse\n")
end
local debut = os.clock()
while fs.free() > 100 do
  local p = vehicle.position()
  local secondes = math.floor(os.clock() - debut)
  fs.append(FICHIER, string.format("%d,%.2f,%.2f,%.2f,%.2f\n", secondes, p.x, p.y, p.z, vehicle.speed()))
  sleep(1)
end
print("Enregistreur plein : copiez vol.csv puis effacez-le")

Une ligne prend de 30 à 40 octets : les 16 Ko d'un Microcontrôleur tiennent environ 7 minutes de vol. De retour à la base, un programme lit le fichier et calcule la distance parcourue, point après point :

Brass
local journal = "temps,x,y,z,vitesse\n0,100.5,80.5,20.5,0\n1,112.5,80.5,25.5,13\n2,124.5,81.5,30.5,13.04\n"
-- dans un vrai programme : local journal = fs.read("vol.csv")
local total, precedent = 0, nil
for i, ligne in ipairs(string.split(journal, "\n")) do
  local c = string.split(ligne, ",")
  if i > 1 and #c == 5 then
    local p = {x = tonumber(c[2]), y = tonumber(c[3]), z = tonumber(c[4])}
    if precedent then
      local dx, dy, dz = p.x - precedent.x, p.y - precedent.y, p.z - precedent.z
      total = total + math.sqrt(dx * dx + dy * dy + dz * dz)
    end
    precedent = p
  end
end
print("Distance parcourue : " .. math.round(total) .. " blocs")
Écran
Distance parcourue : 26 blocs

Voir aussi vehicle.velocity() math.sqrt()

Mouvement

#

vehicle.velocity()

→ table⚙ coût 8

La vitesse de l'ordinateur le long de chaque axe, en blocs par seconde : x vers l'est, y vers le haut, z vers le sud. Les valeurs négatives vont dans l'autre sens (ouest, bas, nord). Au sol, les trois valent 0.

Renvoie
table
{x, y, z}, la vitesse en blocs par seconde

y seul dit si le navire monte ou descend :

Brass
local v = vehicle.velocity()
if v.y > 0.1 then
  print("Monte à " .. v.y .. " blocs/s")
elseif v.y < -0.1 then
  print("Descend à " .. -v.y .. " blocs/s")
else
  print("Vol en palier")
end

x et z donnent le cap, la direction dans laquelle avance le navire, sous forme de relèvement au compas avec math.atan2 : 0 au nord, 90 à l'est, 180 au sud, 270 à l'ouest. Une autre fonction dit de combien tourner pour faire face à une cible, de -180 (tout à gauche) à 180 (tout à droite) :

Brass
local function relevement(dx, dz)
  local degres = math.atan2(dx, -dz) * 180 / math.pi
  return (degres + 360) % 360
end

local function virage(cap, cible)
  return (cible - cap + 540) % 360 - 180
end

print(math.round(relevement(0, -100)))   -- nord
print(math.round(relevement(100, 0)))    -- est
print(math.round(relevement(-50, 50)))   -- sud-ouest
print(virage(350, 10))                   -- 20 degrés à droite
print(virage(10, 350))                   -- 20 degrés à gauche
Écran
0
90
225
20
-20

À bord, relevement(v.x, v.z) est le cap du navire. Un navire immobile n'a pas de cap : vérifiez d'abord sa vitesse. Le programme complet qui ramène à la base se trouve dans les motifs plus bas.

Voir aussi vehicle.speed() math.atan2()

#

vehicle.speed()

→ number⚙ coût 8

La vitesse de l'ordinateur, en blocs par seconde, quelle que soit la direction (la montée et la descente comptent). C'est la longueur de vehicle.velocity(). Au sol, 0.

Renvoie
number
la vitesse en blocs par seconde
Brass
local vitesse = 18.42   -- ce qu'a renvoyé vehicle.speed()
print(vitesse .. " blocs/s")
print(math.round(vitesse * 3.6) .. " km/h")
print(math.round(vitesse * 60) .. " blocs par minute")
Écran
18.42 blocs/s
66 km/h
1105 blocs par minute

Un compteur de vitesse sur un Moniteur LCD. Un Microcontrôleur à bord du dirigeable, un Moniteur LCD contre lui : le moniteur affiche l'écran de l'ordinateur. Le dessin est une fonction, montrée ici avec des valeurs d'exemple :

Brass
local function afficher(nom, vitesse, montee, hauteur)
  term.set_bg(term.colors.black)
  term.clear()
  term.set_fg(term.colors.yellow)
  term.set_cursor(2, 1)
  term.write(nom or "Au sol")
  term.set_fg(term.colors.white)
  term.set_cursor(2, 3)
  term.write(string.format("Vitesse %6.1f blocs/s", vitesse))
  term.set_cursor(2, 4)
  term.write(string.format("        %6.1f km/h", vitesse * 3.6))
  -- une barre de 38 cases pour 0 à 40 blocs/s
  local cases = math.min(38, math.floor(vitesse / 40 * 38))
  term.set_cursor(2, 6)
  term.set_bg(term.colors.lime)
  term.write(string.rep(" ", cases))
  term.set_bg(term.colors.gray)
  term.write(string.rep(" ", 38 - cases))
  term.set_bg(term.colors.black)
  term.set_cursor(2, 8)
  if montee > 0.1 then
    term.set_fg(term.colors.lime)
    term.write(string.format("Monte    %5.1f blocs/s", montee))
  elseif montee < -0.1 then
    term.set_fg(term.colors.orange)
    term.write(string.format("Descend  %5.1f blocs/s", -montee))
  else
    term.set_fg(term.colors.light_gray)
    term.write("Vol en palier")
  end
  term.set_fg(term.colors.white)
  term.set_cursor(2, 10)
  term.write("Altitude " .. math.floor(hauteur))
end

afficher("Albatros", 18.42, 1.25, 143.5)
Écran
Écran

À bord, la même fonction est appelée quatre fois par seconde avec les vraies valeurs (placez cette boucle sous la fonction, dans le même fichier) :

Brass
while true do
  local v = vehicle.velocity()
  afficher(vehicle.name(), vehicle.speed(), v.y, vehicle.position().y)
  sleep(0.25)
end

Voir aussi vehicle.velocity()

Le véhicule

#

vehicle.name()

→ string|nil

Le nom du véhicule Create Aeronautics qui porte l'ordinateur, ou nil au sol (et toujours nil sans Create Aeronautics).

Renvoie
string|nil
le nom du véhicule, ou nil si l'ordinateur n'est pas sur un véhicule
Brass
local nom = vehicle.name()
if nom then
  print("À bord de " .. nom)
else
  print("Pas sur un véhicule")
end

Un nom est bien pratique dans les messages : un navire qui fait son rapport à la base dit qui il est. nil ne peut pas être la valeur d'un champ de table, alors prévoyez un nom de secours :

Brass
local moi = vehicle.name() or os.label() or ("n°" .. net.id())

Voir aussi vehicle.on_vehicle()

#

vehicle.on_vehicle()

→ boolean

true quand l'ordinateur est à bord d'un véhicule. C'est la même chose que vehicle.name() ~= nil.

Renvoie
boolean
true si l'ordinateur est sur un véhicule Create Aeronautics

Un pilote automatique n'a rien à piloter au sol : il peut refuser de démarrer.

Brass
if not vehicle.on_vehicle() then
  print("Posez-moi d'abord sur un dirigeable.")
  return
end
print("Pilote automatique prêt")

Elle dit aussi à un programme qui passe du navire au sol quoi faire : l'écran de vol à bord, la liste du chargement une fois le navire déchargé au quai.

Motifs courants

Rentrer à la base. Chaque seconde, le programme compare le relèvement de la base au cap du navire et dit au pilote de quel côté tourner. Les deux fonctions d'aide sont celles de vehicle.velocity :

Brass
local BASE = {x = 250, z = -1200}   -- l'aire d'atterrissage, lue avec F3

local function relevement(dx, dz)
  local degres = math.atan2(dx, -dz) * 180 / math.pi
  return (degres + 360) % 360
end

local function virage(cap, cible)
  return (cible - cap + 540) % 360 - 180
end

while true do
  local p = vehicle.position()
  local dx, dz = BASE.x - p.x, BASE.z - p.z
  local distance = math.sqrt(dx * dx + dz * dz)
  term.clear()
  term.set_cursor(1, 1)
  if distance < 8 then
    print("Au-dessus de l'aire : posez-vous !")
  else
    local cible = relevement(dx, dz)
    print(string.format("Base : %d blocs, relèvement %d", math.floor(distance), math.floor(cible)))
    local v = vehicle.velocity()
    if math.sqrt(v.x * v.x + v.z * v.z) < 1 then
      print("Avancez pour connaître votre cap")
    else
      local t = virage(relevement(v.x, v.z), cible)
      if t > 10 then
        print(string.format("Tournez à droite de %d degrés", math.floor(t)))
      elseif t < -10 then
        print(string.format("Tournez à gauche de %d degrés", math.floor(-t)))
      else
        print("Tout droit")
      end
    end
  end
  sleep(1)
end

Le cap du navire vient de son mouvement, pas de la direction de sa proue. Une table de navigation (Navigation Table) sur le câble donne directement l'angle vers une cible (navigation_table.angle()).

Un suivi de flotte. Chaque navire diffuse sa position toutes les 2 secondes sur le canal "flotte". Avec un Routeur Wi-Fi sur chaque navire et dans la tour, tous réglés une fois sur le même SSID avec radio.set_ssid(), les routeurs forment un seul réseau dans toute la dimension : la tour entend des navires bien au-delà des 256 blocs d'un routeur seul (voir Réseaux).

Brass
-- sur chaque navire : un Microcontrôleur avec un Routeur Wi-Fi sur son câble
local moi = vehicle.name() or os.label() or ("n°" .. net.id())
while true do
  local p = vehicle.position()
  net.broadcast({type = "position", nom = moi, x = p.x, y = p.y, z = p.z, vitesse = vehicle.speed()}, "flotte")
  sleep(2)
end

La tour garde le dernier rapport de chaque navire. Au sol, vehicle.position() donne la place de la tour elle-même, le centre de la carte. Une ligne du tableau, construite à partir de rapports d'exemple :

Brass
local function ligne(n, ici)
  local dx, dz = n.x - ici.x, n.z - ici.z
  return string.format("%-12.12s %5d %4d %5.1f", n.nom, math.floor(math.sqrt(dx * dx + dz * dz)), math.floor(n.y), n.vitesse)
end

local ici = {x = 0.5, y = 70.5, z = 0.5}   -- vehicle.position() de la tour
print(string.format("%-12s %5s %4s %5s", "NAVIRE", "DIST", "ALT", "VIT."))
print(ligne({nom = "Albatros", x = 300.5, y = 142.5, z = -399.5, vitesse = 18.4}, ici))
print(ligne({nom = "Monte-charge", x = -59.5, y = 96.5, z = 80.5, vitesse = 0}, ici))
Écran
NAVIRE        DIST  ALT  VIT.
Albatros       500  142  18.4
Monte-charge   100   96   0.0

La boucle de la tour (placez-la sous ligne, dans le même fichier) redessine chaque seconde, et signale un navire muet depuis 10 secondes :

Brass
local ici = vehicle.position()
local navires = {}
while true do
  local m = net.receive(1)
  if m and m.channel == "flotte" and type(m.data) == "table" and m.data.type == "position" then
    m.data.quand = os.clock()
    navires[m.sender] = m.data
  end
  term.clear()
  term.set_cursor(1, 1)
  print(string.format("%-12s %5s %4s %5s", "NAVIRE", "DIST", "ALT", "VIT."))
  for _, n in pairs(navires) do
    local texte = ligne(n, ici)
    if os.clock() - n.quand > 10 then texte = texte .. "  perdu" end
    print(texte)
  end
end

Pour un pilote automatique qui tient l'altitude d'un ballon avec les brûleurs à air chaud, voyez Maintien d'altitude d'un dirigeable et le programme intégré balloon.

Voir aussi vehicle.name()