-------------------------------------------------------------------- -- 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