diff --git a/README.md b/README.md index f3b9a51..76c2607 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,14 @@ Via le reseau filaire (wired modems): parametre (Y_VOL, Y_MINI, ANGLE_MAX, DIST_SOL, offsets, joystick, moteur...) puis passer `verifie = true`. Un warning s'affiche tant que ce n'est pas fait. -3. Calibrations depuis le moniteur tactile, DANS CET ORDRE (les - modes non calibres sont verrouilles en rouge). Les calibrations +3. Calibrations depuis l'onglet CALIB, en mode CALIBRAGE pour + celles au sol, DANS CET ORDRE (les modes non calibres sont + verrouilles en rouge). Toutes sont RE-EXECUTABLES (recablage: + relancer la calibration remet ses affectations a zero et prend + effet sans relancer le programme). La calib rsc verifie la + coherence (retour de l'assiette a l'etat initial entre chaque + impulsion, detection des conflits deux RSC -> une meme helice) + et s'annule proprement en cas d'incoherence. Les calibrations assistees affichent leurs consignes sur l'ecran de conduite et se valident en touchant le moniteur: - `gimbal` (assistee, en premier): incliner le nez vers le bas @@ -60,14 +66,29 @@ Via le reseau filaire (wired modems): VITESSE_RAMPE, ANGLE_MAX) INVALIDE la calibration au prochain demarrage (retour aux gains de la conf + alerte). -## Modes (boutons du moniteur; verrouilles en rouge si une -calibration manque, la raison s'affiche au toucher) +## Interface (moniteur tactile) + +Echelle de texte automatique (la plus grande qui loge le contenu). +Navigation par onglets: +- MODE : boutons de modes + contenu adapte au mode courant (voir + ci-dessous), art du quadricoptere (cercles des helices: blanc + rpm=0, rouge rpm max, jaune en changement, vert stable; rpm des + propulseurs sur les cotes) +- CALIB : boutons des calibrations (gris = a faire, jaune = en + cours, vert = faite), consignes et barre de progression, art avec + le RSC en cours en VIOLET +- JOURNAL : dernieres lignes du journal de vol + +## Modes (verrouilles en rouge si une calibration manque, la raison +s'affiche au toucher) - `off` : moteur coupe (bottom a 15), tous les RSC a 0 -- `inactif` : moteur ALLUME, tous les RSC a 0, commandes desactivees, - pret pour les calibrations. Dans tous les modes sauf off, une - erreur s'affiche si le moteur ne tourne pas (stressometer a - capacite nulle) alors qu'il le devrait +- `inactif` : moteur ALLUME, tous les RSC a 0, commandes desactivees. + Dans tous les modes sauf off, une erreur s'affiche si le moteur ne + tourne pas (stressometer a capacite nulle) alors qu'il le devrait +- `calibrage` : identique a inactif mais AUTORISE les calibrations + au sol (gimbal, joystick, typew, rsc); capteurs et pid se lancent + en mode vol - `stationnement` : helices a pleine puissance inversee (plaquage), propulseurs a 0, commandes desactivees. Conditions d'entree: au sol (optical <= DIST_SOL) ET horizontal (<= ANGLE_MAX). Sortie @@ -80,10 +101,12 @@ calibration manque, la raison s'affiche au toucher) - `atterrissage` : descente douce (VITESSE_ATTERRISSAGE) jusqu'au sol, puis stationnement. Sous Y_MINI, la descente exige un sol POSABLE (optical: bloc detecte, ni eau ni lave): garde-fou ocean -- `auto` : au clavier du PC: `auto `. Decollage a Y_VOL, - ralliement en avancant (cap estime par GPS), atterrissage sur place -- `position` : au clavier: `position `. Rallie le point; - la descente est bloquee si le sol est detecte +- `auto` : cible a 2 coordonnees (`auto ` au clavier, pave + tactile, ou bouton d'un point enregistre): decollage a Y_VOL, + ralliement en avancant (cap estime par GPS), atterrissage sur + place. Cible a 3 coordonnees (`auto `): rallie le point + et s'y MAINTIENT a l'altitude y (bornee par le sol et Y_MINI). + Bouton ANNULER: retour en vol stabilise a la position courante La consigne d'altitude se regle aux boutons +-1/+-10 du moniteur ou au TYPEWRITER: taper les chiffres (la saisie s'affiche sur l'ecran @@ -109,8 +132,15 @@ Points nommes (clavier du PC, 10 max): - Carburant: deux valeurs sur le 2e display link: POURCENTAGE de reserve (ligne 1) et AUTONOMIE en minutes (ligne 2), estimee par apprentissage glissant (EMA, CARBURANT.LISSAGE) de la - consommation; les ravitaillements sont exclus de l'apprentissage. - Alerte sous SEUIL_ALERTE. + consommation; les ravitaillements et le mode off sont exclus de + l'apprentissage. Alerte sous SEUIL_ALERTE. +- Moteur: pre-boost a pleine puissance (MOTEUR.BOOST secondes) a + chaque passage vers un regime exigeant (ex: inactif -> + stationnement) pour eviter l'overstress transitoire. +- Reactivite: le GPS (appel bloquant jusqu'a 2 s) est isole dans une + tache dediee alimentant un cache; l'interface et la navigation ne + bloquent jamais dessus. Messages longs de l'ecran de conduite: + defilement par segments de mots entiers (indicateur i/n). ## Fichiers diff --git a/drone.lua b/drone.lua index 3ce5f74..c090497 100644 --- a/drone.lua +++ b/drone.lua @@ -91,11 +91,17 @@ local mode = etat.mode or "off" local consigneY = etat.consigneY or materiel.lireAltitude() local cibleNav = nil local phaseAuto = nil +local distanceCible = nil local stress = nil local carburantPct, autonomieMin = nil, nil local saisieTypewriter = "" +local saisieCible = "" local enCalibration = false +local calibEnCours = nil +local calibProgres, calibTexte = nil, nil +local rscViolet = nil local alerteMoteurA = 0 +local boostMoteurFin = 0 local function sauverEtat() etat.mode, etat.consigneY = mode, consigneY @@ -110,14 +116,14 @@ local function verrouDe(m) local helicesOk = #materiel.helicesManquantes() == 0 local joystickOk = etat.joystick ~= nil - if m == "off" or m == "inactif" then return nil end + if m == "off" or m == "inactif" or m == "calibrage" then return nil end if not gimbalOk then return "calib gimbal requise" end if m == "stationnement" then return nil end if not helicesOk then return "calib rsc requise" end if (m == "vol" or m == "drone") and not joystickOk then return "calib joystick requise" end - if (m == "auto" or m == "position") and not materiel.lirePosition() then + if m == "auto" and not materiel.lirePosition() then return "pas de GPS" end return nil @@ -125,8 +131,8 @@ end local function verrous() local v = {} - for _, m in ipairs({ "off", "inactif", "stationnement", "vol", - "drone", "atterrissage", "auto", "position" }) do + for _, m in ipairs({ "off", "inactif", "calibrage", "stationnement", + "vol", "drone", "atterrissage", "auto" }) do v[m] = verrouDe(m) end return v @@ -151,6 +157,15 @@ local function changerMode(nouveau, raison) return end + -- pre-boost du moteur avant les regimes exigeants (evite + -- l'overstress transitoire, ex: inactif -> stationnement) + local exigeants = { stationnement = true, vol = true, drone = true, + atterrissage = true, auto = true } + if exigeants[nouveau] then + boostMoteurFin = os.clock() + conf.MOTEUR.BOOST + materiel.reglerMoteur(15) + end + mode = nouveau pilotage.raz() navigation.razCap() @@ -161,7 +176,7 @@ local function changerMode(nouveau, raison) phaseAuto = "decollage" local x, y, z = materiel.lirePosition() etat.decollage = { x = x, y = y, z = z } - consigneY = conf.Y_VOL + consigneY = cibleNav.y or conf.Y_VOL elseif mode == "off" then pilotage.arreter() end @@ -202,8 +217,8 @@ local function pasControle() if mode == "off" then pilotage.arreter() - elseif mode == "inactif" then - -- moteur allume, tout a zero, pret pour les calibrations + elseif mode == "inactif" or mode == "calibrage" then + -- moteur allume, tout a zero; calibrage autorise les calibrations materiel.toutArreter() elseif mode == "stationnement" then @@ -234,10 +249,22 @@ local function pasControle() pilotage.reguler(consigneY, { tangage = 0, roulis = 0 }, 0, 0, DT) elseif mode == "auto" then + -- cible a 2 coordonnees (x, z): mission complete avec + -- atterrissage; cible a 3 (x, y, z): rallier et MAINTENIR a y + local yCroisiere = cibleNav.y or conf.Y_VOL + if cibleNav.y and cibleNav.y < materiel.lireAltitude() then + -- descente limitee par la presence d'un sol + if materiel.lireDistanceSol() <= conf.DIST_SOL + 0.5 then + yCroisiere = materiel.lireAltitude() + elseif cibleNav.y < conf.Y_MINI and not materiel.solPosable() then + yCroisiere = conf.Y_MINI + end + end + if phaseAuto == "decollage" then - local alt = pilotage.reguler(conf.Y_VOL, + local alt = pilotage.reguler(yCroisiere, { tangage = 0, roulis = 0 }, 0, 0, DT) - if math.abs(alt - conf.Y_VOL) <= 0.5 then + if math.abs(alt - yCroisiere) <= 0.5 then phaseAuto = "croisiere" journal.info("auto: croisiere vers la cible") end @@ -248,13 +275,25 @@ local function pasControle() changerMode("vol", "GPS perdu") return end - pilotage.reguler(conf.Y_VOL, { tangage = 0, roulis = 0 }, + distanceCible = distance + pilotage.reguler(yCroisiere, { tangage = 0, roulis = 0 }, avance, virage, DT) if distance <= conf.NAV.SEUIL_ARRIVEE then - phaseAuto = "atterrissage" - consigneY = materiel.lireAltitude() - journal.info("auto: cible atteinte, atterrissage") + if cibleNav.y then + phaseAuto = "maintien" + journal.info("auto: cible atteinte, maintien en position") + else + phaseAuto = "atterrissage" + consigneY = materiel.lireAltitude() + journal.info("auto: cible atteinte, atterrissage") + end end + elseif phaseAuto == "maintien" then + local avance, virage, distance = navigation.rallier( + cibleNav.x, cibleNav.z) + distanceCible = distance + pilotage.reguler(yCroisiere, { tangage = 0, roulis = 0 }, + avance or 0, virage or 0, DT) else if materiel.auSol() then changerMode("stationnement", "mission terminee") @@ -263,24 +302,6 @@ local function pasControle() consigneY = descendre() pilotage.reguler(consigneY, { tangage = 0, roulis = 0 }, 0, 0, DT) end - - elseif mode == "position" then - local avance, virage = navigation.rallier(cibleNav.x, cibleNav.z) - if not avance then - changerMode("vol", "GPS perdu") - return - end - local cibleY = cibleNav.y - if cibleY < materiel.lireAltitude() then - -- descente limitee par la presence d'un sol - if materiel.lireDistanceSol() <= conf.DIST_SOL + 0.5 then - cibleY = materiel.lireAltitude() - elseif cibleY < conf.Y_MINI and not materiel.solPosable() then - cibleY = conf.Y_MINI - end - end - pilotage.reguler(cibleY, { tangage = 0, roulis = 0 }, - avance, virage, DT) end end @@ -290,16 +311,32 @@ end local ui = { inviter = function(texte) ihm.message(texte, 3600) + calibTexte = texte pcall(ihm.rafraichirConduite, { mode = mode, consigneY = consigneY }) journal.info("[calib] " .. texte) end, + progres = function(pct, texte) + calibProgres, calibTexte = pct, texte + end, + rscViolet = function(role) rscViolet = role end, attendreToucher = ihm.attendreToucher, } local function lancerCalibration(quoi) enCalibration = true + calibEnCours = quoi + calibProgres, calibTexte = 0, quoi local ok, err = pcall(function() + if quoi == "gimbal" or quoi == "joystick" or quoi == "typew" + or quoi == "rsc" then + if mode ~= "calibrage" then + journal.alerte("calib " .. quoi + .. ": passer en mode calibrage d'abord") + ihm.message("passer en mode calibrage") + return + end + end if quoi == "gimbal" then Calibration.gimbal(conf, etat, materiel, journal, Etat, ui) elseif quoi == "joystick" then @@ -307,17 +344,14 @@ local function lancerCalibration(quoi) elseif quoi == "typew" then Calibration.typewriter(conf, etat, materiel, journal, Etat, ui) elseif quoi == "rsc" then - if mode ~= "inactif" then - journal.alerte("calib rsc: passer en mode inactif d'abord") - else - Calibration.rsc(conf, etat, materiel, journal, Etat, ui) - end + Calibration.rsc(conf, etat, materiel, journal, Etat, ui) elseif quoi == "capteurs" or quoi == "pid" then if mode ~= "vol" then journal.alerte("calib " .. quoi .. ": passer en mode vol d'abord") + ihm.message("passer en mode vol") elseif quoi == "pid" then Calibration.pid(conf, etat, materiel, pilotage, journal, Etat, - empreinte) + empreinte, ui) else local function tenir(duree, angles, avance) local cible = materiel.lireAltitude() @@ -346,9 +380,29 @@ local function lancerCalibration(quoi) end) if not ok then journal.erreur("calibration: " .. tostring(err)) end ihm.message("", 0) + calibEnCours, rscViolet = nil, nil enCalibration = false end +-- statuts des calibrations pour la page CALIB +local function calibStatuts() + local faits = { + gimbal = etat.gimbal ~= nil, + joystick = etat.joystick ~= nil, + typew = etat.typewriter ~= nil, + rsc = #materiel.helicesManquantes() == 0 + and #materiel.propulseursManquants() == 0, + capteurs = #materiel.axesManquants() == 0, + pid = etat.pid ~= nil, + } + local statuts = {} + for nom, fait in pairs(faits) do + if calibEnCours == nom then statuts[nom] = "encours" + elseif fait then statuts[nom] = "fait" end + end + return statuts +end + -------------------------------------------------------------------- -- POINTS NOMMES / RETOUR DECOLLAGE -------------------------------------------------------------------- @@ -408,6 +462,9 @@ local function tacheMoteur() while true do if mode == "off" then materiel.reglerMoteur(0) + elseif os.clock() < boostMoteurFin then + materiel.reglerMoteur(15) + niveau = 15 else stress = materiel.lireStress() if stress then @@ -450,7 +507,7 @@ local function tacheCarburant() carburantPct = quantite / capacite local t = os.clock() - if quantitePrec and t > tPrec then + if quantitePrec and t > tPrec and mode ~= "off" then local conso = (quantitePrec - quantite) / (t - tPrec) if conso >= 0 then -- conso negative = ravitaillement: ignore local a = conf.CARBURANT.LISSAGE @@ -525,39 +582,77 @@ local function tacheTypewriter() end end +-- GPS: seule tache autorisee a faire l'appel bloquant (jusqu'a 2 s) +local function tachePosition() + while true do + if mode ~= "off" then + pcall(materiel.actualiserPosition) + end + sleep(3) + end +end + +local function contexteIhm() + local px, py, pz = materiel.lirePosition() + return { + mode = mode, + consigneY = consigneY, + stress = stress, + moteurOk = mode == "off" or materiel.moteurTourne(), + carburantPct = carburantPct, + minutes = autonomieMin, + position = px and { x = px, y = py, z = pz } or nil, + points = etat.points, + verrous = verrous(), + calibStatuts = calibStatuts(), + calibProgres = calibProgres, + calibTexte = calibTexte, + rscViolet = rscViolet, + cible = cibleNav and (cibleNav.y + and ("%d %d %d"):format(cibleNav.x, cibleNav.y, cibleNav.z) + or ("%d %d"):format(cibleNav.x, cibleNav.z)) or nil, + distanceCible = distanceCible, + saisieCible = saisieCible, + } +end + +-- lance la cible saisie: "x z" ou "x y z" +local function lancerCible(texte) + local nombres = {} + for n in texte:gmatch("%-?%d+") do table.insert(nombres, tonumber(n)) end + if #nombres == 2 then + cibleNav = { x = nombres[1], z = nombres[2] } + changerMode("auto", "cible x z") + elseif #nombres == 3 then + cibleNav = { x = nombres[1], y = nombres[2], z = nombres[3] } + changerMode("auto", "cible x y z") + else + ihm.message("format: x z ou x y z") + end +end + local function tacheIhm() local prochainDessin = 0 + local redessiner = false while true do - if os.clock() >= prochainDessin and not enCalibration then - local px, py, pz = materiel.lirePosition() + if (os.clock() >= prochainDessin or redessiner) then + redessiner = false pcall(ihm.rafraichirConduite, { mode = mode, consigneY = consigneY, saisie = saisieTypewriter, }) - pcall(ihm.rafraichirMoniteur, { - mode = mode, - consigneY = consigneY, - stress = stress, - moteurOk = mode == "off" or materiel.moteurTourne(), - gimbalOk = etat.gimbal ~= nil, - carburantPct = carburantPct, - minutes = autonomieMin, - position = px and { x = px, y = py, z = pz } or nil, - points = nbPoints(), - verrous = verrous(), - }) - prochainDessin = os.clock() + 0.5 + pcall(ihm.rafraichirMoniteur, contexteIhm()) + prochainDessin = os.clock() + 1.0 end - local minuteur = os.startTimer(0.5) + local minuteur = os.startTimer(0.25) local ev, a, b, c = os.pullEvent() - if ev == "monitor_touch" and not enCalibration then - local action = ihm.traiterToucher(b, c) + if ev == "monitor_touch" then + local action = not enCalibration and ihm.traiterToucher(b, c) if action then + redessiner = true if action.type == "mode" then if action.verrou then ihm.message(action.valeur .. ": " .. action.verrou) - elseif action.valeur == "auto" or action.valeur == "position" then - ihm.message("cible au clavier: " .. action.valeur .. " x [y] z") else changerMode(action.valeur, "moniteur") end @@ -566,6 +661,23 @@ local function tacheIhm() sauverEtat() elseif action.type == "calib" then lancerCalibration(action.valeur) + elseif action.type == "ptgo" then + local p = etat.points[action.nom] + if p then + cibleNav = { x = p.x, z = p.z } + changerMode("auto", "pt " .. action.nom) + end + elseif action.type == "annuler" then + changerMode("vol", "annulation auto") + elseif action.type == "pave" then + if action.valeur == "eff" then + saisieCible = saisieCible:sub(1, -2) + elseif action.valeur == "go" then + lancerCible(saisieCible) + saisieCible = "" + else + saisieCible = (saisieCible .. action.valeur):sub(1, 18) + end end end end @@ -579,13 +691,13 @@ local function tacheClavier() local mots = {} for mot in ligne:gmatch("%S+") do table.insert(mots, mot) end if mots[1] == "auto" and tonumber(mots[2]) and tonumber(mots[3]) then - cibleNav = { x = tonumber(mots[2]), z = tonumber(mots[3]) } + if tonumber(mots[4]) then + cibleNav = { x = tonumber(mots[2]), y = tonumber(mots[3]), + z = tonumber(mots[4]) } + else + cibleNav = { x = tonumber(mots[2]), z = tonumber(mots[3]) } + end changerMode("auto", "clavier") - elseif mots[1] == "position" and tonumber(mots[2]) - and tonumber(mots[3]) and tonumber(mots[4]) then - cibleNav = { x = tonumber(mots[2]), y = tonumber(mots[3]), - z = tonumber(mots[4]) } - changerMode("position", "clavier") elseif mots[1] == "pt" then commandePoint(mots) elseif mots[1] == "rtb" then @@ -596,7 +708,7 @@ local function tacheClavier() print("pas de point de decollage enregistre") end elseif mots[1] then - print("auto | position | pt ... | rtb") + print("auto | auto | pt ... | rtb") end end end @@ -607,4 +719,4 @@ if mode == "vol" or mode == "drone" then consigneY = materiel.lireAltitude() end parallel.waitForAny(tacheControle, tacheMoteur, tacheCarburant, - tacheTypewriter, tacheIhm, tacheClavier) + tachePosition, tacheTypewriter, tacheIhm, tacheClavier) diff --git a/lib/calibration.lua b/lib/calibration.lua index 4b85e04..213f531 100644 --- a/lib/calibration.lua +++ b/lib/calibration.lua @@ -27,10 +27,12 @@ end function Calibration.gimbal(conf, etat, materiel, journal, Etat, ui) local seuil = conf.CALIB.IMPULSION_SEUIL + ui.progres(0, "mise a plat") ui.inviter("Drone a plat, touchez l'ecran") ui.attendreToucher() local x0, z0 = materiel.lireAnglesBruts() + ui.progres(0.25, "nez bas") ui.inviter("Inclinez NEZ vers le BAS, touchez") ui.attendreToucher() local x1, z1 = materiel.lireAnglesBruts() @@ -45,10 +47,12 @@ function Calibration.gimbal(conf, etat, materiel, journal, Etat, ui) -- convention: nez bas => tangage normalise NEGATIF local signeTangage = (deltaT < 0) and 1 or -1 + ui.progres(0.5, "retour a plat") ui.inviter("Revenez a plat, touchez") ui.attendreToucher() x0, z0 = materiel.lireAnglesBruts() + ui.progres(0.75, "penche a gauche") ui.inviter("Penchez a GAUCHE, touchez") ui.attendreToucher() x1, z1 = materiel.lireAnglesBruts() @@ -69,6 +73,7 @@ function Calibration.gimbal(conf, etat, materiel, journal, Etat, ui) Etat.sauver(etat) journal.info(("calib gimbal: tangage=angle%d(x%+d) roulis=angle%d(x%+d)") :format(indexTangage, signeTangage, indexRoulis, signeRoulis)) + ui.progres(1, "terminee") ui.inviter("Gimbal calibre, remettez a plat") return true end @@ -105,7 +110,8 @@ function Calibration.joystick(conf, etat, materiel, journal, Etat, ui) end local exclues = {} - for _, direction in ipairs({ "devant", "derriere", "gauche", "droite" }) do + for idx, direction in ipairs({ "devant", "derriere", "gauche", "droite" }) do + ui.progres((idx - 1) / 4, direction) ui.inviter("Joystick a fond: " .. direction:upper()) local face = nil while not face do @@ -121,6 +127,7 @@ function Calibration.joystick(conf, etat, materiel, journal, Etat, ui) etat.joystick = mapping Etat.sauver(etat) + ui.progres(1, "terminee") ui.inviter("Joystick calibre") journal.info("calib joystick: terminee") return true @@ -143,7 +150,8 @@ function Calibration.typewriter(conf, etat, materiel, journal, Etat, ui) local symboles = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "valider", "effacer" } - for _, symbole in ipairs(symboles) do + for idx, symbole in ipairs(symboles) do + ui.progres((idx - 1) / #symboles, symbole) ui.inviter("Typewriter: appuyez sur " .. symbole:upper()) local code = nil while not code do @@ -161,6 +169,7 @@ function Calibration.typewriter(conf, etat, materiel, journal, Etat, ui) etat.typewriter = mapping Etat.sauver(etat) + ui.progres(1, "terminee") ui.inviter("Typewriter calibre") journal.info("calib typewriter: terminee") return true @@ -181,20 +190,51 @@ function Calibration.rsc(conf, etat, materiel, journal, Etat, ui) materiel.toutArreter() local C = conf.CALIB - local nonAffectes = {} - for nom in pairs(materiel.rsc) do - if not etat.roles[nom] then table.insert(nonAffectes, nom) end - end - table.sort(nonAffectes) - if #nonAffectes == 0 then - journal.info("calib rsc: tous les RSC sont deja affectes") - return true + -- RECALIBRATION COMPLETE: on repart de zero (recablage possible) + local anciensRoles = etat.roles + etat.roles = {} + materiel.rafraichirAffectations() + + local noms = {} + for nom in pairs(materiel.rsc) do table.insert(noms, nom) end + table.sort(noms) + + -- reference "a plat" pour le retour a l'etat initial entre mesures + sleep(1.0) + local refT, refR = materiel.lireAssiette() + local function retourInitial() + local fin = os.clock() + 10 + while os.clock() < fin do + local t, r = materiel.lireAssiette() + if math.abs(t - refT) < C.IMPULSION_SEUIL / 2 + and math.abs(r - refR) < C.IMPULSION_SEUIL / 2 then + sleep(0.5) + return true + end + sleep(0.2) + end + return false end + local function abandonner(raison) + materiel.toutArreter() + etat.roles = anciensRoles + materiel.rafraichirAffectations() + journal.erreur("calib rsc annulee: " .. raison) + ui.progres(0, "ECHEC: " .. raison) + return false + end + + local affectes = {} -- role -> nom (controle d'unicite) local propulseurs = {} - for _, nom in ipairs(nonAffectes) do + + for i, nom in ipairs(noms) do + ui.progres((i - 1) / #noms, "impulsion " .. nom) + if not retourInitial() then + return abandonner("assiette instable avant " .. nom) + end + journal.info("calib rsc: impulsion sur " .. nom) - ui.inviter("Impulsion: " .. nom) local p = materiel.rsc[nom] local t0, r0 = materiel.lireAssiette() local vitesse, dt_, dr = 0, 0, 0 @@ -211,20 +251,26 @@ function Calibration.rsc(conf, etat, materiel, journal, Etat, ui) end end p.setTargetSpeed(0) - sleep(1.0) if math.abs(dt_) >= C.IMPULSION_SEUIL and math.abs(dr) >= C.IMPULSION_SEUIL then -- assiette normalisee: tangage>0 = nez haut => helice a l'AVANT -- roulis>0 = penche a droite => helice a GAUCHE local role = ((dr > 0) and "l" or "r") .. ((dt_ > 0) and "f" or "b") + if affectes[role] then + return abandonner(("conflit: %s et %s -> %s") + :format(affectes[role], nom, role)) + end + affectes[role] = nom etat.roles[nom] = role + ui.rscViolet(role) journal.info(" -> helice " .. role) else table.insert(propulseurs, nom) journal.info(" -> pas d'effet d'assiette: propulseur") end end + ui.rscViolet(nil) if #propulseurs == 2 then etat.roles[propulseurs[1]] = "prop_l" @@ -232,13 +278,14 @@ function Calibration.rsc(conf, etat, materiel, journal, Etat, ui) journal.alerte("calib rsc: prop_l/prop_r affectes ARBITRAIREMENT: " .. "si un virage gauche part a droite, les echanger dans drone.etat") elseif #propulseurs > 0 then - journal.alerte(("calib rsc: %d propulseur(s), 2 attendus") + return abandonner(("%d propulseur(s) detecte(s), 2 attendus") :format(#propulseurs)) end Etat.sauver(etat) - journal.info("calib rsc: terminee (relancer drone pour recharger)") - ui.inviter("RSC calibres: relancer drone") + materiel.rafraichirAffectations() -- prise d'effet immediate + ui.progres(1, "terminee") + journal.info("calib rsc: terminee") return true end @@ -286,26 +333,35 @@ function Calibration.capteurs(conf, etat, materiel, journal, Etat, ui, local function signeDe(moyenne) return (moyenne >= 0) and 1 or -1 end + -- RECALIBRATION COMPLETE + etat.velocite = {} + materiel.rafraichirAffectations() + + ui.progres(0, "montee...") ui.inviter("Calib capteurs: montee...") local capteur, moyenne = mesurerPendant(mouvements.monter, {}) etat.velocite[capteur] = { axe = "vertical", signe = signeDe(moyenne) } journal.info((" vertical: %s (x%+d)"):format(capteur, signeDe(moyenne))) local exclus = { [capteur] = true } + ui.progres(0.33, "avancee...") ui.inviter("Calib capteurs: avancee...") capteur, moyenne = mesurerPendant(mouvements.avancer, exclus) etat.velocite[capteur] = { axe = "avant", signe = signeDe(moyenne) } journal.info((" avant: %s (x%+d)"):format(capteur, signeDe(moyenne))) exclus[capteur] = true + ui.progres(0.66, "translation gauche...") ui.inviter("Calib capteurs: translation gauche...") capteur, moyenne = mesurerPendant(mouvements.gauche, exclus) etat.velocite[capteur] = { axe = "lateral", signe = signeDe(moyenne) } journal.info((" lateral: %s (x%+d)"):format(capteur, signeDe(moyenne))) Etat.sauver(etat) - ui.inviter("Capteurs calibres: relancer drone") - journal.info("calib capteurs: terminee (relancer drone pour recharger)") + materiel.rafraichirAffectations() + ui.progres(1, "terminee") + ui.inviter("Capteurs calibres") + journal.info("calib capteurs: terminee") return true end @@ -313,7 +369,7 @@ end -- PID D'ALTITUDE (en vol stationnaire, marge d'altitude requise) -------------------------------------------------------------------- function Calibration.pid(conf, etat, materiel, pilotage, journal, Etat, - empreinte) + empreinte, ui) local C = conf.CALIB local base = materiel.lireAltitude() local gains = pilotage.gains() @@ -363,6 +419,9 @@ function Calibration.pid(conf, etat, materiel, pilotage, journal, Etat, local meilleurs, meilleurTemps = nil, math.huge for iter = 1, C.MAX_ITER do + local base01 = (dir.sens > 0) and 0 or 0.5 + ui.progres(base01 + (iter - 1) / C.MAX_ITER / 2, + ("%s %d/%d"):format(dir.nom, iter, C.MAX_ITER)) journal.info(("[calib %s %d/%d] kp=%.1f ki=%.1f kd=%.1f") :format(dir.nom, iter, C.MAX_ITER, g.kp, g.ki, g.kd)) pilotage.reglerGains(gains) @@ -414,6 +473,7 @@ function Calibration.pid(conf, etat, materiel, pilotage, journal, Etat, if math.abs(alt - base) <= C.BANDE then break end sleep(dt) end + ui.progres(1, "terminee") journal.info("calib pid: terminee") return true end diff --git a/lib/conf.lua b/lib/conf.lua index 5d00da9..7373a2a 100644 --- a/lib/conf.lua +++ b/lib/conf.lua @@ -89,6 +89,8 @@ return { STRESS_BAS = 0.60, -- en deca: reduire le moteur (economie) SURCHARGE = 0.98, -- au dela: reduire les propulseurs + alerte PERIODE = 1.0, -- periode de l'asservissement (s) + BOOST = 4.0, -- pre-boost a pleine puissance lors des + -- changements de regime exigeants (s) }, ------------------------------------------------------------------ diff --git a/lib/ihm.lua b/lib/ihm.lua index 76a7bef..e5a4ec2 100644 --- a/lib/ihm.lua +++ b/lib/ihm.lua @@ -1,43 +1,80 @@ -------------------------------------------------------------------- -- lib/ihm.lua : interfaces homme-machine. --- Display Link "conduite" : altitudes, vitesse, invites, alertes --- Display Link "carburant" : reserve + autonomie estimee (minutes) --- Advanced monitor tactile : modes (verrouilles si calibration --- manquante), consigne, calibrations, informations --- Typewriter : la saisie est geree dans drone.lua (scrutation) +-- Moniteur tactile : navigation par onglets (MODE / CALIB / +-- JOURNAL), echelle de texte auto (la plus grande qui loge le +-- contenu), pages adaptees au mode, art du quadricoptere. +-- Display Link conduite : altitudes / vitesse / messages longs +-- defiles par segments de mots entiers. +-- Display Link carburant : % + autonomie. -------------------------------------------------------------------- local Ihm = {} +-- couleur d'un rpm: blanc=0, rouge=max, jaune=en changement, vert=stable +local function couleurRpm(suivi, vmax) + if not suivi then return colors.lightGray end + if os.clock() - suivi.tChange < 1.0 then return colors.yellow end + if suivi.v == 0 then return colors.white end + if math.abs(suivi.v) >= vmax then return colors.red end + return colors.green +end + function Ihm.nouveau(conf, materiel, journal) local ihm = {} local moniteur = materiel.moniteur - moniteur.setTextScale(0.5) - local zones = {} - local messageConduite, messageFin = nil, 0 + local page = "mode" -- mode | calib | journal + local L, H = 0, 0 -- taille en caracteres + + -- Echelle auto: la PLUS GRANDE echelle offrant au moins 38x14 + local function reglerEchelle() + for _, s in ipairs({ 5, 4.5, 4, 3.5, 3, 2.5, 2, 1.5, 1, 0.5 }) do + moniteur.setTextScale(s) + L, H = moniteur.getSize() + if L >= 38 and H >= 14 then return end + end + end + reglerEchelle() + + ------------------------------------------------------------------ + -- MESSAGES DE CONDUITE (defilement par segments de mots entiers) + ------------------------------------------------------------------ + local segments, segmentFin, segmentIdx = nil, 0, 1 - -- message temporaire sur la ligne 1 de la conduite (invites, - -- alertes); duree en secondes (nil = 5) function ihm.message(texte, duree) - messageConduite = texte - messageFin = os.clock() + (duree or 5) + if texte == "" then segments = nil return end + -- decoupe en segments <= 22 caracteres sans couper les mots + segments = {} + local courant = "" + for mot in texte:gmatch("%S+") do + if #courant == 0 then courant = mot + elseif #courant + 1 + #mot <= 22 then courant = courant .. " " .. mot + else table.insert(segments, courant) courant = mot end + end + if #courant > 0 then table.insert(segments, courant) end + segmentIdx = 1 + segmentFin = os.clock() + (duree or math.max(5, #segments * 2)) + end + + local function segmentCourant() + if not segments or os.clock() > segmentFin then return nil end + -- avance d'un segment toutes les 2 s + local i = 1 + math.floor((os.clock() % (#segments * 2)) / 2) + return segments[math.min(i, #segments)] + .. (#segments > 1 and (" " .. i .. "/" .. #segments) or "") end - ------------------------------------------------------------------ - -- ECRAN DE CONDUITE - ------------------------------------------------------------------ -- ctx: { mode, consigneY, saisie } function ihm.rafraichirConduite(ctx) local e = materiel.ecranConduite e.clear() e.setCursorPos(1, 1) - if messageConduite and os.clock() < messageFin then - e.write(messageConduite:sub(1, 22)) + local seg = segmentCourant() + if seg then + e.write(seg:sub(1, 22)) elseif ctx.saisie and #ctx.saisie > 0 then - e.write("SAISIE: " .. ctx.saisie) + e.write(("SAISIE: " .. ctx.saisie):sub(1, 22)) elseif ctx.mode == "vol" or ctx.mode == "drone" - or ctx.mode == "atterrissage" or ctx.mode == "auto" - or ctx.mode == "position" then + or ctx.mode == "atterrissage" or ctx.mode == "auto" then e.write(("ALT %.1f / %.0f"):format( materiel.lireAltitude(), ctx.consigneY or 0)) else @@ -48,10 +85,6 @@ function Ihm.nouveau(conf, materiel, journal) e.update() end - ------------------------------------------------------------------ - -- ECRAN CARBURANT (peut etre tres etroit: format compact) - ------------------------------------------------------------------ - -- pct 0..1 ou nil ; minutes ou nil function ihm.rafraichirCarburant(pct, minutes) local e = materiel.ecranCarburant local largeur = e.getSize() @@ -62,23 +95,22 @@ function Ihm.nouveau(conf, materiel, journal) else e.write(("%d%%"):format(math.floor(pct * 100 + 0.5)):sub(1, largeur)) e.setCursorPos(1, 2) - if minutes then - e.write(("%dm"):format(math.floor(minutes)):sub(1, largeur)) - else - e.write(("--m"):sub(1, largeur)) - end + e.write((minutes and ("%dm"):format(math.floor(minutes)) or "--m") + :sub(1, largeur)) end e.update() end ------------------------------------------------------------------ - -- MONITEUR TACTILE + -- PRIMITIVES MONITEUR ------------------------------------------------------------------ - local function bouton(x, y, texte, action, couleur) + local function bouton(x, y, texte, action, couleur, couleurTexte) moniteur.setCursorPos(x, y) moniteur.setBackgroundColor(couleur or colors.gray) + moniteur.setTextColor(couleurTexte or colors.white) moniteur.write(" " .. texte .. " ") moniteur.setBackgroundColor(colors.black) + moniteur.setTextColor(colors.white) table.insert(zones, { x1 = x, y1 = y, x2 = x + #texte + 1, y2 = y, action = action, }) @@ -91,78 +123,245 @@ function Ihm.nouveau(conf, materiel, journal) moniteur.setTextColor(colors.white) end - -- ctx: { mode, consigneY, stress, position, carburantPct, minutes, - -- moteurOk, verrous = { mode -> raison|nil }, points = n } - function ihm.rafraichirMoniteur(ctx) - zones = {} - moniteur.setBackgroundColor(colors.black) - moniteur.clear() + ------------------------------------------------------------------ + -- ART DU QUADRICOPTERE + -- rectangle, 4 cercles aux coins (helices, rpm a cote), rpm des + -- propulseurs sur les cotes; violet = RSC en cours de calibration + ------------------------------------------------------------------ + local function couleurDe(role, ctx) + if ctx.rscViolet == role then return colors.purple end + return couleurRpm(materiel.rpm[role], conf.VITESSE_RSC_MAX) + end - texte(1, 1, "== MODES == actuel: " .. ctx.mode) - local modes = { "off", "inactif", "stationnement", "vol", "drone", - "atterrissage", "auto", "position" } + local function rpmDe(role) + local s = materiel.rpm[role] + return s and tostring(s.v) or "?" + end + + -- occupe 7 lignes a partir de y; largeur ~ 30 + local function dessinerArt(x, y, ctx) + local function coin(cx, cy, role, rpmAGauche) + texte(cx, cy, "O", couleurDe(role, ctx)) + local r = rpmDe(role) + if rpmAGauche then texte(cx - #r - 1, cy, r, couleurDe(role, ctx)) + else texte(cx + 2, cy, r, couleurDe(role, ctx)) end + end + local g, d = x + 5, x + 18 -- colonnes des coins + texte(x + 9, y, "AVANT", colors.lightGray) + coin(g, y + 1, "lf", true) + coin(d, y + 1, "rf", false) + for i = 2, 4 do + texte(g, y + i, "|") + texte(d, y + i, "|") + end + texte(g + 1, y + 1, ("-"):rep(d - g - 1)) + texte(g + 1, y + 5, ("-"):rep(d - g - 1)) + texte(x, y + 3, rpmDe("prop_l"), couleurDe("prop_l", ctx)) + texte(d + 2, y + 3, rpmDe("prop_r"), couleurDe("prop_r", ctx)) + coin(g, y + 5, "lb", true) + coin(d, y + 5, "rb", false) + return y + 7 + end + + ------------------------------------------------------------------ + -- PAGE MODE (contenu adapte au mode courant) + ------------------------------------------------------------------ + local function blocCommun(y, ctx) + texte(1, y, ("carburant %s autonomie %s"):format( + ctx.carburantPct and ("%.0f%%"):format(ctx.carburantPct * 100) + or "n/a", + ctx.minutes and ("%dmin"):format(math.floor(ctx.minutes)) or "--")) + if ctx.position then + texte(1, y + 1, ("gps %.0f / %.0f"):format( + ctx.position.x, ctx.position.z)) + else + texte(1, y + 1, "gps n/a", colors.red) + end + return y + 2 + end + + local function blocStress(y, ctx) + local brut, cap = materiel.lireStressBrut() + texte(1, y, ("stress %s / %s (%s) moteur %s"):format( + brut and ("%d"):format(brut) or "?", + cap and ("%d"):format(cap) or "?", + ctx.stress and ("%.0f%%"):format(ctx.stress * 100) or "n/a", + ctx.moteurOk and "ok" or "ARRETE"), + ctx.moteurOk and colors.white or colors.red) + return y + 1 + end + + local function dessinerPageMode(ctx) + local modes = { "off", "inactif", "calibrage", "stationnement", + "vol", "drone", "atterrissage", "auto" } local x, y = 1, 2 for _, m in ipairs(modes) do local verrou = ctx.verrous[m] local couleur = colors.gray if m == ctx.mode then couleur = colors.green elseif verrou then couleur = colors.red end - if x + #m + 2 > 50 then x, y = 1, y + 1 end + if x + #m + 2 > L then x, y = 1, y + 1 end bouton(x, y, m, { type = "mode", valeur = m, verrou = verrou }, couleur) x = x + #m + 3 end + y = y + 2 - texte(1, y + 2, ("== CONSIGNE == %.0f"):format(ctx.consigneY or 0)) - bouton(1, y + 3, "-10", { type = "delta", valeur = -10 }) - bouton(7, y + 3, "-1", { type = "delta", valeur = -1 }) - bouton(12, y + 3, "+1", { type = "delta", valeur = 1 }) - bouton(17, y + 3, "+10", { type = "delta", valeur = 10 }) + local mode = ctx.mode + y = blocCommun(y, ctx) + if mode ~= "off" then y = blocStress(y, ctx) end + y = y + 1 - texte(1, y + 5, "== CALIBRATIONS ==") - local cx = 1 + if mode == "stationnement" or mode == "vol" or mode == "drone" + or mode == "auto" then + y = dessinerArt(1, y, ctx) + end + + if mode == "vol" or mode == "drone" then + texte(1, y, ("vit %.2f b/s alt %.1f -> %.0f"):format( + materiel.vitesseHorizontale(), materiel.lireAltitude(), + ctx.consigneY or 0)) + y = y + 1 + bouton(1, y, "-10", { type = "delta", valeur = -10 }) + bouton(7, y, "-1", { type = "delta", valeur = -1 }) + bouton(12, y, "+1", { type = "delta", valeur = 1 }) + bouton(17, y, "+10", { type = "delta", valeur = 10 }) + y = y + 1 + if mode == "drone" then + texte(1, y, "INCLINAISON LIBRE (joystick = assiette)", + colors.orange) + y = y + 1 + end + elseif mode == "atterrissage" then + texte(1, y, ("distance au sol: %.1f"):format( + materiel.lireDistanceSol())) + y = y + 1 + elseif mode == "auto" then + texte(1, y, ("vit %.2f b/s alt %.1f cible %s dist %s"):format( + materiel.vitesseHorizontale(), materiel.lireAltitude(), + ctx.cible or "-", ctx.distanceCible + and ("%.0f"):format(ctx.distanceCible) or "-")) + y = y + 1 + -- points enregistres en boutons + annulation + local x2 = 1 + for nom in pairs(ctx.points or {}) do + if x2 + #nom + 2 > L - 10 then break end + bouton(x2, y, nom, { type = "ptgo", nom = nom }, colors.blue) + x2 = x2 + #nom + 3 + end + bouton(L - 9, y, "ANNULER", { type = "annuler" }, colors.red) + y = y + 1 + -- zone de saisie + pave numerique (coordonnees: "x z" ou "x y z") + texte(1, y, "coord: " .. (ctx.saisieCible or "") .. "_") + y = y + 1 + local paves = { "1","2","3","4","5","6","7","8","9","0","-" } + local x3 = 1 + for _, p in ipairs(paves) do + bouton(x3, y, p, { type = "pave", valeur = p }) + x3 = x3 + 4 + end + bouton(x3, y, "esp", { type = "pave", valeur = " " }) + x3 = x3 + 6 + bouton(x3, y, "eff", { type = "pave", valeur = "eff" }) + x3 = x3 + 6 + bouton(x3, y, "GO", { type = "pave", valeur = "go" }, colors.green) + end + end + + ------------------------------------------------------------------ + -- PAGE CALIBRATIONS + ------------------------------------------------------------------ + local function dessinerPageCalib(ctx) + texte(1, 2, "gris: a faire jaune: en cours vert: fait", + colors.lightGray) + local x, y = 1, 3 for _, c in ipairs({ "gimbal", "joystick", "typew", "rsc", "capteurs", "pid" }) do - bouton(cx, y + 6, c, { type = "calib", valeur = c }) - cx = cx + #c + 3 + local statut = ctx.calibStatuts[c] + local couleur = colors.gray + if statut == "encours" then couleur = colors.yellow + elseif statut == "fait" then couleur = colors.green end + if x + #c + 2 > L then x, y = 1, y + 1 end + bouton(x, y, c, { type = "calib", valeur = c }, couleur, + statut == "encours" and colors.black or colors.white) + x = x + #c + 3 end + y = y + 2 - local ly = y + 8 - texte(1, ly, "== INFOS ==") - local t, r = materiel.lireAssiette() - texte(1, ly + 1, ("assiette T %+.2f R %+.2f%s"):format(t, r, - ctx.gimbalOk and "" or " (NON CALIBRE)")) - texte(1, ly + 2, ("sol %.1f (%s%s)"):format( - materiel.lireDistanceSol(), - materiel.auSol() and "au sol" or "en vol", - materiel.solPosable() and "" or ", NON POSABLE")) - texte(1, ly + 3, ("stress %s moteur %s"):format( - ctx.stress and ("%.0f%%"):format(ctx.stress * 100) or "n/a", - ctx.moteurOk and "ok" or "ARRETE"), - ctx.moteurOk and colors.white or colors.red) - texte(1, ly + 4, ("carburant %s autonomie %s"):format( - ctx.carburantPct - and ("%.0f%%"):format(ctx.carburantPct * 100) or "n/a", - ctx.minutes and ("%dmin"):format(math.floor(ctx.minutes)) or "--")) - if ctx.position then - texte(1, ly + 5, ("gps %.0f %.0f %.0f points: %d") - :format(ctx.position.x, ctx.position.y, ctx.position.z, - ctx.points or 0)) - else - texte(1, ly + 5, "gps n/a", colors.red) + if ctx.calibTexte then + texte(1, y, ctx.calibTexte:sub(1, L), colors.yellow) + y = y + 1 end + if ctx.calibProgres then + local largeur = L - 2 + local rempli = math.floor(ctx.calibProgres * largeur + 0.5) + moniteur.setCursorPos(1, y) + moniteur.setBackgroundColor(colors.green) + moniteur.write((" "):rep(rempli)) + moniteur.setBackgroundColor(colors.gray) + moniteur.write((" "):rep(largeur - rempli)) + moniteur.setBackgroundColor(colors.black) + y = y + 2 + end + if ctx.rscViolet then + texte(1, y, "en violet: RSC en cours", colors.purple) + y = dessinerArt(1, y + 1, ctx) + end + texte(1, H - 1, "sol: gimbal joystick typew rsc (mode calibrage)", + colors.lightGray) + texte(1, H, "vol: capteurs pid (mode vol)", colors.lightGray) + end + + ------------------------------------------------------------------ + -- PAGE JOURNAL + ------------------------------------------------------------------ + local function dessinerPageJournal() + local lignes = journal.recents(H - 2) + for i, ligne in ipairs(lignes) do + texte(1, 1 + i, ligne:sub(1, L), + ligne:find("ERREUR") and colors.red + or ligne:find("ALERTE") and colors.orange or colors.white) + end + end + + ------------------------------------------------------------------ + -- RENDU GLOBAL + ------------------------------------------------------------------ + function ihm.rafraichirMoniteur(ctx) + zones = {} + moniteur.setBackgroundColor(colors.black) + moniteur.clear() + + -- barre d'onglets + local onglets = { mode = "MODE", calib = "CALIB", journal = "JOURNAL" } + local x = 1 + for _, id in ipairs({ "mode", "calib", "journal" }) do + bouton(x, 1, onglets[id], { type = "page", valeur = id }, + page == id and colors.green or colors.gray) + x = x + #onglets[id] + 3 + end + texte(math.min(x + 1, L - #ctx.mode), 1, ctx.mode, colors.lightGray) + + if page == "mode" then dessinerPageMode(ctx) + elseif page == "calib" then dessinerPageCalib(ctx) + else dessinerPageJournal() end end function ihm.traiterToucher(tx, ty) for _, z in ipairs(zones) do if tx >= z.x1 and tx <= z.x2 and ty >= z.y1 and ty <= z.y2 then + if z.action.type == "page" then + page = z.action.valeur + return { type = "redessiner" } + end return z.action end end return nil end - -- attend un toucher du moniteur (utilise par les calibrations) + function ihm.reglerPage(p) page = p end + function ihm.attendreToucher() while true do local ev = os.pullEvent("monitor_touch") diff --git a/lib/journal.lua b/lib/journal.lua index 477c3c6..a2bf729 100644 --- a/lib/journal.lua +++ b/lib/journal.lua @@ -4,6 +4,20 @@ local Journal = {} local FICHIER, MAX_TAILLE = "drone.log", 64 * 1024 +local recents, MAX_RECENTS = {}, 60 + +local function memoriser(ligne) + table.insert(recents, ligne) + if #recents > MAX_RECENTS then table.remove(recents, 1) end +end + +-- dernieres lignes (les plus recentes en dernier) +function Journal.recents(n) + local depart = math.max(1, #recents - (n or MAX_RECENTS) + 1) + local lignes = {} + for i = depart, #recents do table.insert(lignes, recents[i]) end + return lignes +end local function horodater(texte) return ("[%s] %s"):format(textutils.formatTime(os.time("local"), true), texte) @@ -25,18 +39,21 @@ function Journal.info(texte) local ligne = horodater(texte) print(ligne) ecrireFichier(ligne) + memoriser(ligne) end function Journal.erreur(texte) local ligne = horodater("ERREUR " .. texte) printError(ligne) ecrireFichier(ligne) + memoriser(ligne) end function Journal.alerte(texte) local ligne = horodater("ALERTE " .. texte) printError(ligne) ecrireFichier(ligne) + memoriser(ligne) end return Journal diff --git a/lib/materiel.lua b/lib/materiel.lua index 94a6057..d6b9d50 100644 --- a/lib/materiel.lua +++ b/lib/materiel.lua @@ -25,6 +25,9 @@ end function Materiel.initialiser(conf, etat, journal) local m = {} + m.rpm = {} -- role -> { v, tChange } (pour l'affichage) + local posCache = nil, nil + local posDate = 0 m.altitude = trouver("altitude_sensor", true) m.gimbal = trouver("gimbal_sensor", true) @@ -48,27 +51,31 @@ function Materiel.initialiser(conf, etat, journal) m.ecranConduite = ecran(conf.ECRANS.conduite, "conduite") m.ecranCarburant = ecran(conf.ECRANS.carburant, "carburant") - -- rotation speed controllers - m.rsc, m.rscParRole = {}, {} + -- rotation speed controllers et velocity sensors: les affectations + -- (etat.roles / etat.velocite) sont rechargeables A CHAUD apres + -- une recalibration via m.rafraichirAffectations() + m.rsc, m.velocite = {}, {} for _, nom in ipairs(peripheral.getNames()) do - if peripheral.getType(nom) == "Create_RotationSpeedController" then + local type_ = peripheral.getType(nom) + if type_ == "Create_RotationSpeedController" then m.rsc[nom] = peripheral.wrap(nom) - local role = etat.roles[nom] - if role then m.rscParRole[role] = m.rsc[nom] end + elseif type_ == "velocity_sensor" then + m.velocite[nom] = peripheral.wrap(nom) end end - -- velocity sensors - m.velocite, m.veloParAxe = {}, {} - for _, nom in ipairs(peripheral.getNames()) do - if peripheral.getType(nom) == "velocity_sensor" then - m.velocite[nom] = peripheral.wrap(nom) + function m.rafraichirAffectations() + m.rscParRole, m.veloParAxe = {}, {} + for nom, p in pairs(m.rsc) do + local role = etat.roles[nom] + if role then m.rscParRole[role] = p end + end + for nom, p in pairs(m.velocite) do local aff = etat.velocite[nom] - if aff then - m.veloParAxe[aff.axe] = { p = m.velocite[nom], signe = aff.signe } - end + if aff then m.veloParAxe[aff.axe] = { p = p, signe = aff.signe } end end end + m.rafraichirAffectations() -- cuves de carburant (create:fluid_tank; exclut le moteur diesel, -- qui est fluid_storage mais pas fluid_tank) @@ -177,6 +184,12 @@ function Materiel.initialiser(conf, etat, journal) return m.stresso.getStress() / cap end + -- valeurs brutes (su) pour l'affichage: stress, capacite + 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: capacite 0 a l'arret) function m.moteurTourne() if not m.stresso then return true end -- indetectable: on suppose oui @@ -197,11 +210,21 @@ function Materiel.initialiser(conf, etat, journal) return quantite, capacite end - -- position GPS corrigee des offsets, ou nil - function m.lirePosition() + -- ACTUALISATION GPS (BLOQUANTE, jusqu'a 2 s): a appeler depuis une + -- tache dediee uniquement + function m.actualiserPosition() local x, y, z = gps.locate(2) - if not x then return nil end - return x + conf.OFFSET_X, y + conf.OFFSET_Y, z + conf.OFFSET_Z + if x then + posCache = { x = x + conf.OFFSET_X, y = y + conf.OFFSET_Y, + z = z + conf.OFFSET_Z } + posDate = os.clock() + end + end + + -- position GPS en CACHE (non bloquante); nil si absente ou > 8 s + function m.lirePosition() + if not posCache or os.clock() - posDate > 8 then return nil end + return posCache.x, posCache.y, posCache.z end ------------------------------------------------------------------ @@ -214,11 +237,20 @@ function Materiel.initialiser(conf, etat, journal) function m.reglerRsc(role, vitesse) local p = m.rscParRole[role] - if p then p.setTargetSpeed(math.floor(vitesse + 0.5)) end + if not p then return end + vitesse = 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 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 ------------------------------------------------------------------