383 lines
12 KiB
Lua
383 lines
12 KiB
Lua
--------------------------------------------------------------------
|
|
-- lib/materiel.lua : acces au materiel.
|
|
-- Peripheriques: 6 RSC (4 helices + 2 propulseurs), gimbal,
|
|
-- altitude (top), optical (vers le sol), stressometer, redstone
|
|
-- relay (joystick), moniteur tactile, 2 display links (conduite
|
|
-- 2x22, carburant 2x4), velocity sensors (vitesse globale), moteur
|
|
-- diesel pilote par redstone analogique bottom (15=arret, 0=maxi).
|
|
--------------------------------------------------------------------
|
|
local Materiel = {}
|
|
|
|
function Materiel.initialiser(conf, etat, journal)
|
|
local m = {}
|
|
m.rpm = {} -- role -> { v, tChange } (affichage + besoin moteur)
|
|
|
|
local function premier(type_)
|
|
for _, nom in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(nom) == type_ then
|
|
return peripheral.wrap(nom), nom
|
|
end
|
|
end
|
|
end
|
|
|
|
m.altitude = premier("altitude_sensor")
|
|
m.gimbal = premier("gimbal_sensor")
|
|
m.optical = premier("optical_sensor")
|
|
m.stresso = premier("Create_Stressometer")
|
|
m.moniteur = premier("monitor")
|
|
|
|
-- relays multiples: joystick sur l'un, boites de vitesse sur un
|
|
-- autre (identifies par leurs calibrations respectives)
|
|
m.relays = {}
|
|
for _, nom in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(nom) == "redstone_relay" then
|
|
m.relays[nom] = peripheral.wrap(nom)
|
|
end
|
|
end
|
|
-- Les NOMS des relays peuvent changer (renumerotation reseau);
|
|
-- les FACES, elles, sont stables. Resolution robuste:
|
|
-- joystick: nom stocke s'il existe encore, sinon "un relay qui
|
|
-- n'est pas celui des boites", sinon le premier
|
|
local function relayJoystick()
|
|
local j = etat.joystick
|
|
if j and j.relais and m.relays[j.relais] then
|
|
return m.relays[j.relais]
|
|
end
|
|
local boite = etat.boite and etat.boite.relais
|
|
for nom, p in pairs(m.relays) do
|
|
if nom ~= boite then return p end
|
|
end
|
|
for _, p in pairs(m.relays) do return p end
|
|
end
|
|
|
|
-- les noms stockes correspondent-ils encore au reseau ?
|
|
function m.relaisValides()
|
|
if etat.boite and not m.relays[etat.boite.relais] then
|
|
return false
|
|
end
|
|
if etat.joystick and etat.joystick.relais
|
|
and not m.relays[etat.joystick.relais] then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
for _, obligatoire in ipairs({ "altitude", "gimbal", "moniteur" }) do
|
|
if not m[obligatoire] then
|
|
error("peripherique manquant: " .. obligatoire, 0)
|
|
end
|
|
end
|
|
|
|
-- display links: conduite = le plus large, carburant = l'autre
|
|
local liens = {}
|
|
for _, nom in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(nom) == "Create_DisplayLink" then
|
|
table.insert(liens, peripheral.wrap(nom))
|
|
end
|
|
end
|
|
table.sort(liens, function(a, b)
|
|
return (a.getSize() or 0) > (b.getSize() or 0)
|
|
end)
|
|
m.ecranConduite, m.ecranCarburant = liens[1], liens[2]
|
|
|
|
-- RSC et velocity sensors
|
|
m.rsc, m.velocite = {}, {}
|
|
for _, nom in ipairs(peripheral.getNames()) do
|
|
local type_ = peripheral.getType(nom)
|
|
if type_ == "Create_RotationSpeedController" then
|
|
m.rsc[nom] = peripheral.wrap(nom)
|
|
elseif type_ == "velocity_sensor" then
|
|
m.velocite[nom] = peripheral.wrap(nom)
|
|
end
|
|
end
|
|
|
|
-- speedometers (vitesse REELLE des helices, apres boite)
|
|
m.speedo = {}
|
|
for _, nom in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(nom) == "Create_Speedometer" then
|
|
m.speedo[nom] = peripheral.wrap(nom)
|
|
end
|
|
end
|
|
|
|
function m.lireSpeedo(nom)
|
|
local p = m.speedo[nom]
|
|
if not p then return nil end
|
|
return p.getSpeed() or 0
|
|
end
|
|
|
|
-- vitesse reelle d'une helice via son speedometer associe
|
|
function m.lireVitesseHelice(role)
|
|
local assoc = etat.speedoParRole and etat.speedoParRole[role]
|
|
if not assoc then return nil end
|
|
return m.lireSpeedo(assoc)
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- BOITES DE VITESSE (analog transmission):
|
|
-- signal 0 -> rapport 16/16 ; sinon rapport = (15-signal)/16
|
|
-- signal 15 -> rapport 0: INTERDIT (helices stoppees)
|
|
-- Les 4 boites partagent UNE face d'UN relay (meme rapport pour
|
|
-- les 4: indispensable a la symetrie). Le rapport est choisi AU
|
|
-- SOL (calib altitude) et JAMAIS change en vol.
|
|
------------------------------------------------------------------
|
|
-- etat.boite = { relais, faces = { lb, rb, lf, rf -> face },
|
|
-- signal, rapport } : une face PAR boite, meme
|
|
-- signal (donc meme rapport) sur les 4
|
|
function m.rapportBoite()
|
|
local b = etat.boite
|
|
if not b then return 1 end
|
|
return b.rapport or 1
|
|
end
|
|
|
|
function m.signalPourRapportK(k)
|
|
if k >= 16 then return 0 end
|
|
return 15 - k
|
|
end
|
|
|
|
-- ecrit le signal sur les 4 faces des boites
|
|
function m.appliquerBoite()
|
|
local b = etat.boite
|
|
if not b or not m.relays[b.relais] or not b.faces then return end
|
|
for _, face in pairs(b.faces) do
|
|
m.relays[b.relais].setAnalogOutput(face, b.signal or 0)
|
|
end
|
|
end
|
|
|
|
function m.reglerSignalBoite(nomRelais, face, signal)
|
|
local p = m.relays[nomRelais]
|
|
if p then p.setAnalogOutput(face, signal) end
|
|
end
|
|
|
|
-- affectations rechargeables A CHAUD apres recalibration
|
|
function m.rafraichirAffectations()
|
|
m.rscParRole, m.nomParRole = {}, {}
|
|
for nom, p in pairs(m.rsc) do
|
|
local role = etat.roles[nom]
|
|
if role then
|
|
m.rscParRole[role] = p
|
|
m.nomParRole[role] = nom
|
|
end
|
|
end
|
|
end
|
|
m.rafraichirAffectations()
|
|
|
|
function m.helicesManquantes()
|
|
local manquantes = {}
|
|
for _, role in ipairs({ "lb", "rb", "lf", "rf" }) do
|
|
if not m.rscParRole[role] then table.insert(manquantes, role) end
|
|
end
|
|
return manquantes
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- LECTURES
|
|
------------------------------------------------------------------
|
|
function m.lireAltitude()
|
|
return m.altitude.getHeight()
|
|
end
|
|
|
|
function m.lirePression()
|
|
return m.altitude.getAirPressure() or 1.0
|
|
end
|
|
|
|
-- angles bruts du gimbal: { a1, a2 } (mapping variable par
|
|
-- vaisseau, d'ou la calibration)
|
|
function m.lireAnglesBruts()
|
|
local a = m.gimbal.getAngles()
|
|
return a[1] or 0, a[2] or 0
|
|
end
|
|
|
|
-- assiette NORMALISEE par la calib gimbal:
|
|
-- tangage NEGATIF = nez bas, roulis NEGATIF = penche a gauche
|
|
function m.lireAssiette()
|
|
local g = etat.gimbal
|
|
if not g then return 0, 0 end
|
|
local a = m.gimbal.getAngles()
|
|
return (a[g.indexTangage] or 0) * g.signeTangage,
|
|
(a[g.indexRoulis] or 0) * g.signeRoulis
|
|
end
|
|
|
|
-- distance au sol (optical vers le bas); 15.5 = rien devant
|
|
function m.lireDistanceSol()
|
|
if not m.optical then return 99 end
|
|
local d = m.optical.getDistance()
|
|
if not d or d >= 15.4 then return 99 end
|
|
return d
|
|
end
|
|
|
|
function m.auSol()
|
|
return m.lireDistanceSol() <= conf.DIST_SOL
|
|
end
|
|
|
|
function m.lireStressBrut()
|
|
if not m.stresso then return nil end
|
|
return m.stresso.getStress(), m.stresso.getStressCapacity()
|
|
end
|
|
|
|
-- capacite > 0 <=> le moteur tourne (verifie: 0 a l'arret)
|
|
function m.moteurTourne()
|
|
if not m.stresso then return true end
|
|
local _, capacite = m.lireStressBrut()
|
|
return (capacite or 0) > 0
|
|
end
|
|
|
|
-- vitesse GLOBALE = sqrt(v1^2 + v2^2 + v3^2): les directions des
|
|
-- capteurs n'ont pas d'importance
|
|
function m.vitesseGlobale()
|
|
local somme = 0
|
|
for _, p in pairs(m.velocite) do
|
|
local v = p.getVelocity() or 0
|
|
somme = somme + v * v
|
|
end
|
|
return math.sqrt(somme)
|
|
end
|
|
|
|
function m.lireVitesseCapteur(nom)
|
|
local p = m.velocite[nom]
|
|
return p and (p.getVelocity() or 0) or 0
|
|
end
|
|
|
|
-- joystick via redstone relay (calib: direction -> face)
|
|
-- retourne avance (-1..1, devant positif) et virage (-1..1,
|
|
-- droite positif); 0,0 si non calibre
|
|
function m.lireJoystick()
|
|
local j = etat.joystick
|
|
local relay = relayJoystick()
|
|
if not j or not j.faces or not relay then return 0, 0 end
|
|
local function niveau(direction)
|
|
local face = j.faces[direction]
|
|
if not face then return 0 end
|
|
return (relay.getAnalogInput(face) or 0) / 15
|
|
end
|
|
return niveau("devant") - niveau("derriere"),
|
|
niveau("droite") - niveau("gauche")
|
|
end
|
|
|
|
-- lecture d'une face d'un relay nomme (calibrations)
|
|
function m.lireFaceRelayDe(nomRelais, face)
|
|
local p = m.relays[nomRelais]
|
|
if not p then return 0 end
|
|
return p.getAnalogInput(face) or 0
|
|
end
|
|
|
|
-- carburant: cuves du reseau (le moteur diesel en bottom est
|
|
-- aussi un fluid_storage: exclu du comptage)
|
|
function m.lireCarburant()
|
|
local quantite, capacite = 0, 0
|
|
for _, nom in ipairs(peripheral.getNames()) do
|
|
if nom ~= "bottom"
|
|
and peripheral.hasType(nom, "fluid_storage") then
|
|
local p = peripheral.wrap(nom)
|
|
for _, cuve in ipairs(p.tanks() or {}) do
|
|
quantite = quantite + (cuve.amount or 0)
|
|
end
|
|
if p.getInfo then
|
|
local info = p.getInfo()
|
|
capacite = capacite + (info and info.capacity or 0)
|
|
else
|
|
capacite = capacite + 16000
|
|
end
|
|
end
|
|
end
|
|
if capacite <= 0 then return nil end
|
|
return quantite / capacite, quantite
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- COMMANDES
|
|
------------------------------------------------------------------
|
|
local HELICES = { lb = true, rb = true, lf = true, rf = true }
|
|
|
|
-- vitesse en RPM HELICE pour les 4 helices (conversion RSC =
|
|
-- helice / rapport a la frontiere de l'actionneur: le regulateur
|
|
-- travaille en unites physiques, invariantes au rapport), en rpm
|
|
-- RSC pour les propulseurs (pas de boite)
|
|
function m.reglerRsc(role, vitesse)
|
|
local p = m.rscParRole[role]
|
|
if not p then return end
|
|
if HELICES[role] then vitesse = vitesse / m.rapportBoite() end
|
|
vitesse = math.max(-256, math.min(256,
|
|
math.floor(vitesse + 0.5)))
|
|
local suivi = m.rpm[role]
|
|
if not suivi or suivi.v ~= vitesse then
|
|
m.rpm[role] = { v = vitesse, tChange = os.clock() }
|
|
end
|
|
p.setTargetSpeed(vitesse)
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- CHANGEMENT DE RAPPORT EN VOL (ordre SUR: le transitoire d'un
|
|
-- tick est toujours un CREUX de poussee, jamais un pic)
|
|
-- k monte (rapport augmente): RSC reecrits D'ABORD (avec le
|
|
-- nouveau rapport: valeurs plus basses, creux), redstone
|
|
-- ENSUITE (restaure)
|
|
-- k descend: redstone D'ABORD (creux), RSC reecrits ENSUITE
|
|
-- rpmHelices: role -> rpm HELICE a maintenir pendant la bascule
|
|
------------------------------------------------------------------
|
|
function m.changerRapport(k, rpmHelices)
|
|
local b = etat.boite
|
|
if not b then return false end
|
|
if k == 15 then k = (b.rapport * 16 < 15) and 14 or 16 end
|
|
k = math.max(1, math.min(16, k))
|
|
local nouveauRapport = k / 16
|
|
if nouveauRapport == b.rapport then return false end
|
|
local monte = nouveauRapport > b.rapport
|
|
|
|
local function ecrireRsc()
|
|
for role in pairs(HELICES) do
|
|
local p = m.rscParRole[role]
|
|
local v = rpmHelices[role]
|
|
if p and v then
|
|
local rsc = math.max(-256, math.min(256,
|
|
math.floor(v / nouveauRapport + 0.5)))
|
|
m.rpm[role] = { v = rsc, tChange = os.clock() }
|
|
p.setTargetSpeed(rsc)
|
|
end
|
|
end
|
|
end
|
|
|
|
local signal = m.signalPourRapportK(k)
|
|
local function ecrireFaces()
|
|
for _, face in pairs(b.faces or {}) do
|
|
m.reglerSignalBoite(b.relais, face, signal)
|
|
end
|
|
end
|
|
if monte then
|
|
ecrireRsc()
|
|
ecrireFaces()
|
|
else
|
|
ecrireFaces()
|
|
ecrireRsc()
|
|
end
|
|
b.signal, b.rapport = signal, nouveauRapport
|
|
return true
|
|
end
|
|
|
|
function m.toutArreter()
|
|
for _, p in pairs(m.rsc) do p.setTargetSpeed(0) end
|
|
for role, suivi in pairs(m.rpm) do
|
|
if suivi.v ~= 0 then
|
|
m.rpm[role] = { v = 0, tChange = os.clock() }
|
|
end
|
|
end
|
|
end
|
|
|
|
-- niveau 0 = arret, 15 = pleine puissance (redstone inverse:
|
|
-- signal bottom 15 = arret, 0 = maxi)
|
|
local niveauCourant = -1
|
|
function m.reglerMoteur(niveau)
|
|
niveau = math.max(0, math.min(15, math.floor(niveau + 0.5)))
|
|
if niveau ~= niveauCourant then
|
|
redstone.setAnalogOutput("bottom", 15 - niveau)
|
|
niveauCourant = niveau
|
|
end
|
|
end
|
|
|
|
function m.niveauMoteur()
|
|
return math.max(0, niveauCourant)
|
|
end
|
|
|
|
return m
|
|
end
|
|
|
|
return Materiel
|