cctweaked_drone/lib/materiel.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