Update Programme

This commit is contained in:
Ploush 2026-07-17 18:07:54 +02:00
parent 6032da4967
commit 7298c8642d
7 changed files with 643 additions and 191 deletions

View file

@ -32,8 +32,14 @@ Via le reseau filaire (wired modems):
parametre (Y_VOL, Y_MINI, ANGLE_MAX, DIST_SOL, offsets, joystick, parametre (Y_VOL, Y_MINI, ANGLE_MAX, DIST_SOL, offsets, joystick,
moteur...) puis passer `verifie = true`. Un warning s'affiche tant moteur...) puis passer `verifie = true`. Un warning s'affiche tant
que ce n'est pas fait. que ce n'est pas fait.
3. Calibrations depuis le moniteur tactile, DANS CET ORDRE (les 3. Calibrations depuis l'onglet CALIB, en mode CALIBRAGE pour
modes non calibres sont verrouilles en rouge). Les calibrations 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 assistees affichent leurs consignes sur l'ecran de conduite et se
valident en touchant le moniteur: valident en touchant le moniteur:
- `gimbal` (assistee, en premier): incliner le nez vers le bas - `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 VITESSE_RAMPE, ANGLE_MAX) INVALIDE la calibration au prochain
demarrage (retour aux gains de la conf + alerte). demarrage (retour aux gains de la conf + alerte).
## Modes (boutons du moniteur; verrouilles en rouge si une ## Interface (moniteur tactile)
calibration manque, la raison s'affiche au toucher)
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 - `off` : moteur coupe (bottom a 15), tous les RSC a 0
- `inactif` : moteur ALLUME, tous les RSC a 0, commandes desactivees, - `inactif` : moteur ALLUME, tous les RSC a 0, commandes desactivees.
pret pour les calibrations. Dans tous les modes sauf off, une Dans tous les modes sauf off, une erreur s'affiche si le moteur ne
erreur s'affiche si le moteur ne tourne pas (stressometer a tourne pas (stressometer a capacite nulle) alors qu'il le devrait
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), - `stationnement` : helices a pleine puissance inversee (plaquage),
propulseurs a 0, commandes desactivees. Conditions d'entree: au sol propulseurs a 0, commandes desactivees. Conditions d'entree: au sol
(optical <= DIST_SOL) ET horizontal (<= ANGLE_MAX). Sortie (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 - `atterrissage` : descente douce (VITESSE_ATTERRISSAGE) jusqu'au
sol, puis stationnement. Sous Y_MINI, la descente exige un sol sol, puis stationnement. Sous Y_MINI, la descente exige un sol
POSABLE (optical: bloc detecte, ni eau ni lave): garde-fou ocean POSABLE (optical: bloc detecte, ni eau ni lave): garde-fou ocean
- `auto` : au clavier du PC: `auto <x> <z>`. Decollage a Y_VOL, - `auto` : cible a 2 coordonnees (`auto <x> <z>` au clavier, pave
ralliement en avancant (cap estime par GPS), atterrissage sur place tactile, ou bouton d'un point enregistre): decollage a Y_VOL,
- `position` : au clavier: `position <x> <y> <z>`. Rallie le point; ralliement en avancant (cap estime par GPS), atterrissage sur
la descente est bloquee si le sol est detecte place. Cible a 3 coordonnees (`auto <x> <y> <z>`): 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 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 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 - Carburant: deux valeurs sur le 2e display link: POURCENTAGE de
reserve (ligne 1) et AUTONOMIE en minutes (ligne 2), estimee par reserve (ligne 1) et AUTONOMIE en minutes (ligne 2), estimee par
apprentissage glissant (EMA, CARBURANT.LISSAGE) de la apprentissage glissant (EMA, CARBURANT.LISSAGE) de la
consommation; les ravitaillements sont exclus de l'apprentissage. consommation; les ravitaillements et le mode off sont exclus de
Alerte sous SEUIL_ALERTE. 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 ## Fichiers

224
drone.lua
View file

@ -91,11 +91,17 @@ local mode = etat.mode or "off"
local consigneY = etat.consigneY or materiel.lireAltitude() local consigneY = etat.consigneY or materiel.lireAltitude()
local cibleNav = nil local cibleNav = nil
local phaseAuto = nil local phaseAuto = nil
local distanceCible = nil
local stress = nil local stress = nil
local carburantPct, autonomieMin = nil, nil local carburantPct, autonomieMin = nil, nil
local saisieTypewriter = "" local saisieTypewriter = ""
local saisieCible = ""
local enCalibration = false local enCalibration = false
local calibEnCours = nil
local calibProgres, calibTexte = nil, nil
local rscViolet = nil
local alerteMoteurA = 0 local alerteMoteurA = 0
local boostMoteurFin = 0
local function sauverEtat() local function sauverEtat()
etat.mode, etat.consigneY = mode, consigneY etat.mode, etat.consigneY = mode, consigneY
@ -110,14 +116,14 @@ local function verrouDe(m)
local helicesOk = #materiel.helicesManquantes() == 0 local helicesOk = #materiel.helicesManquantes() == 0
local joystickOk = etat.joystick ~= nil 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 not gimbalOk then return "calib gimbal requise" end
if m == "stationnement" then return nil end if m == "stationnement" then return nil end
if not helicesOk then return "calib rsc requise" end if not helicesOk then return "calib rsc requise" end
if (m == "vol" or m == "drone") and not joystickOk then if (m == "vol" or m == "drone") and not joystickOk then
return "calib joystick requise" return "calib joystick requise"
end end
if (m == "auto" or m == "position") and not materiel.lirePosition() then if m == "auto" and not materiel.lirePosition() then
return "pas de GPS" return "pas de GPS"
end end
return nil return nil
@ -125,8 +131,8 @@ end
local function verrous() local function verrous()
local v = {} local v = {}
for _, m in ipairs({ "off", "inactif", "stationnement", "vol", for _, m in ipairs({ "off", "inactif", "calibrage", "stationnement",
"drone", "atterrissage", "auto", "position" }) do "vol", "drone", "atterrissage", "auto" }) do
v[m] = verrouDe(m) v[m] = verrouDe(m)
end end
return v return v
@ -151,6 +157,15 @@ local function changerMode(nouveau, raison)
return return
end 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 mode = nouveau
pilotage.raz() pilotage.raz()
navigation.razCap() navigation.razCap()
@ -161,7 +176,7 @@ local function changerMode(nouveau, raison)
phaseAuto = "decollage" phaseAuto = "decollage"
local x, y, z = materiel.lirePosition() local x, y, z = materiel.lirePosition()
etat.decollage = { x = x, y = y, z = z } etat.decollage = { x = x, y = y, z = z }
consigneY = conf.Y_VOL consigneY = cibleNav.y or conf.Y_VOL
elseif mode == "off" then elseif mode == "off" then
pilotage.arreter() pilotage.arreter()
end end
@ -202,8 +217,8 @@ local function pasControle()
if mode == "off" then if mode == "off" then
pilotage.arreter() pilotage.arreter()
elseif mode == "inactif" then elseif mode == "inactif" or mode == "calibrage" then
-- moteur allume, tout a zero, pret pour les calibrations -- moteur allume, tout a zero; calibrage autorise les calibrations
materiel.toutArreter() materiel.toutArreter()
elseif mode == "stationnement" then elseif mode == "stationnement" then
@ -234,10 +249,22 @@ local function pasControle()
pilotage.reguler(consigneY, { tangage = 0, roulis = 0 }, 0, 0, DT) pilotage.reguler(consigneY, { tangage = 0, roulis = 0 }, 0, 0, DT)
elseif mode == "auto" then 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 if phaseAuto == "decollage" then
local alt = pilotage.reguler(conf.Y_VOL, local alt = pilotage.reguler(yCroisiere,
{ tangage = 0, roulis = 0 }, 0, 0, DT) { 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" phaseAuto = "croisiere"
journal.info("auto: croisiere vers la cible") journal.info("auto: croisiere vers la cible")
end end
@ -248,13 +275,25 @@ local function pasControle()
changerMode("vol", "GPS perdu") changerMode("vol", "GPS perdu")
return return
end end
pilotage.reguler(conf.Y_VOL, { tangage = 0, roulis = 0 }, distanceCible = distance
pilotage.reguler(yCroisiere, { tangage = 0, roulis = 0 },
avance, virage, DT) avance, virage, DT)
if distance <= conf.NAV.SEUIL_ARRIVEE then if distance <= conf.NAV.SEUIL_ARRIVEE then
if cibleNav.y then
phaseAuto = "maintien"
journal.info("auto: cible atteinte, maintien en position")
else
phaseAuto = "atterrissage" phaseAuto = "atterrissage"
consigneY = materiel.lireAltitude() consigneY = materiel.lireAltitude()
journal.info("auto: cible atteinte, atterrissage") journal.info("auto: cible atteinte, atterrissage")
end 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 else
if materiel.auSol() then if materiel.auSol() then
changerMode("stationnement", "mission terminee") changerMode("stationnement", "mission terminee")
@ -263,24 +302,6 @@ local function pasControle()
consigneY = descendre() consigneY = descendre()
pilotage.reguler(consigneY, { tangage = 0, roulis = 0 }, 0, 0, DT) pilotage.reguler(consigneY, { tangage = 0, roulis = 0 }, 0, 0, DT)
end 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
end end
@ -290,16 +311,32 @@ end
local ui = { local ui = {
inviter = function(texte) inviter = function(texte)
ihm.message(texte, 3600) ihm.message(texte, 3600)
calibTexte = texte
pcall(ihm.rafraichirConduite, pcall(ihm.rafraichirConduite,
{ mode = mode, consigneY = consigneY }) { mode = mode, consigneY = consigneY })
journal.info("[calib] " .. texte) journal.info("[calib] " .. texte)
end, end,
progres = function(pct, texte)
calibProgres, calibTexte = pct, texte
end,
rscViolet = function(role) rscViolet = role end,
attendreToucher = ihm.attendreToucher, attendreToucher = ihm.attendreToucher,
} }
local function lancerCalibration(quoi) local function lancerCalibration(quoi)
enCalibration = true enCalibration = true
calibEnCours = quoi
calibProgres, calibTexte = 0, quoi
local ok, err = pcall(function() 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 if quoi == "gimbal" then
Calibration.gimbal(conf, etat, materiel, journal, Etat, ui) Calibration.gimbal(conf, etat, materiel, journal, Etat, ui)
elseif quoi == "joystick" then elseif quoi == "joystick" then
@ -307,17 +344,14 @@ local function lancerCalibration(quoi)
elseif quoi == "typew" then elseif quoi == "typew" then
Calibration.typewriter(conf, etat, materiel, journal, Etat, ui) Calibration.typewriter(conf, etat, materiel, journal, Etat, ui)
elseif quoi == "rsc" then 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) Calibration.rsc(conf, etat, materiel, journal, Etat, ui)
end
elseif quoi == "capteurs" or quoi == "pid" then elseif quoi == "capteurs" or quoi == "pid" then
if mode ~= "vol" then if mode ~= "vol" then
journal.alerte("calib " .. quoi .. ": passer en mode vol d'abord") journal.alerte("calib " .. quoi .. ": passer en mode vol d'abord")
ihm.message("passer en mode vol")
elseif quoi == "pid" then elseif quoi == "pid" then
Calibration.pid(conf, etat, materiel, pilotage, journal, Etat, Calibration.pid(conf, etat, materiel, pilotage, journal, Etat,
empreinte) empreinte, ui)
else else
local function tenir(duree, angles, avance) local function tenir(duree, angles, avance)
local cible = materiel.lireAltitude() local cible = materiel.lireAltitude()
@ -346,9 +380,29 @@ local function lancerCalibration(quoi)
end) end)
if not ok then journal.erreur("calibration: " .. tostring(err)) end if not ok then journal.erreur("calibration: " .. tostring(err)) end
ihm.message("", 0) ihm.message("", 0)
calibEnCours, rscViolet = nil, nil
enCalibration = false enCalibration = false
end 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 -- POINTS NOMMES / RETOUR DECOLLAGE
-------------------------------------------------------------------- --------------------------------------------------------------------
@ -408,6 +462,9 @@ local function tacheMoteur()
while true do while true do
if mode == "off" then if mode == "off" then
materiel.reglerMoteur(0) materiel.reglerMoteur(0)
elseif os.clock() < boostMoteurFin then
materiel.reglerMoteur(15)
niveau = 15
else else
stress = materiel.lireStress() stress = materiel.lireStress()
if stress then if stress then
@ -450,7 +507,7 @@ local function tacheCarburant()
carburantPct = quantite / capacite carburantPct = quantite / capacite
local t = os.clock() 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) local conso = (quantitePrec - quantite) / (t - tPrec)
if conso >= 0 then -- conso negative = ravitaillement: ignore if conso >= 0 then -- conso negative = ravitaillement: ignore
local a = conf.CARBURANT.LISSAGE local a = conf.CARBURANT.LISSAGE
@ -525,39 +582,77 @@ local function tacheTypewriter()
end end
end end
local function tacheIhm() -- GPS: seule tache autorisee a faire l'appel bloquant (jusqu'a 2 s)
local prochainDessin = 0 local function tachePosition()
while true do while true do
if os.clock() >= prochainDessin and not enCalibration then if mode ~= "off" then
pcall(materiel.actualiserPosition)
end
sleep(3)
end
end
local function contexteIhm()
local px, py, pz = materiel.lirePosition() local px, py, pz = materiel.lirePosition()
pcall(ihm.rafraichirConduite, { return {
mode = mode, consigneY = consigneY, saisie = saisieTypewriter,
})
pcall(ihm.rafraichirMoniteur, {
mode = mode, mode = mode,
consigneY = consigneY, consigneY = consigneY,
stress = stress, stress = stress,
moteurOk = mode == "off" or materiel.moteurTourne(), moteurOk = mode == "off" or materiel.moteurTourne(),
gimbalOk = etat.gimbal ~= nil,
carburantPct = carburantPct, carburantPct = carburantPct,
minutes = autonomieMin, minutes = autonomieMin,
position = px and { x = px, y = py, z = pz } or nil, position = px and { x = px, y = py, z = pz } or nil,
points = nbPoints(), points = etat.points,
verrous = verrous(), 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 or redessiner) then
redessiner = false
pcall(ihm.rafraichirConduite, {
mode = mode, consigneY = consigneY, saisie = saisieTypewriter,
}) })
prochainDessin = os.clock() + 0.5 pcall(ihm.rafraichirMoniteur, contexteIhm())
prochainDessin = os.clock() + 1.0
end end
local minuteur = os.startTimer(0.5) local minuteur = os.startTimer(0.25)
local ev, a, b, c = os.pullEvent() local ev, a, b, c = os.pullEvent()
if ev == "monitor_touch" and not enCalibration then if ev == "monitor_touch" then
local action = ihm.traiterToucher(b, c) local action = not enCalibration and ihm.traiterToucher(b, c)
if action then if action then
redessiner = true
if action.type == "mode" then if action.type == "mode" then
if action.verrou then if action.verrou then
ihm.message(action.valeur .. ": " .. action.verrou) 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 else
changerMode(action.valeur, "moniteur") changerMode(action.valeur, "moniteur")
end end
@ -566,6 +661,23 @@ local function tacheIhm()
sauverEtat() sauverEtat()
elseif action.type == "calib" then elseif action.type == "calib" then
lancerCalibration(action.valeur) 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 end
end end
@ -579,13 +691,13 @@ local function tacheClavier()
local mots = {} local mots = {}
for mot in ligne:gmatch("%S+") do table.insert(mots, mot) end 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 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
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]), cibleNav = { x = tonumber(mots[2]), y = tonumber(mots[3]),
z = tonumber(mots[4]) } z = tonumber(mots[4]) }
changerMode("position", "clavier") else
cibleNav = { x = tonumber(mots[2]), z = tonumber(mots[3]) }
end
changerMode("auto", "clavier")
elseif mots[1] == "pt" then elseif mots[1] == "pt" then
commandePoint(mots) commandePoint(mots)
elseif mots[1] == "rtb" then elseif mots[1] == "rtb" then
@ -596,7 +708,7 @@ local function tacheClavier()
print("pas de point de decollage enregistre") print("pas de point de decollage enregistre")
end end
elseif mots[1] then elseif mots[1] then
print("auto <x> <z> | position <x> <y> <z> | pt ... | rtb") print("auto <x> <z> | auto <x> <y> <z> | pt ... | rtb")
end end
end end
end end
@ -607,4 +719,4 @@ if mode == "vol" or mode == "drone" then
consigneY = materiel.lireAltitude() consigneY = materiel.lireAltitude()
end end
parallel.waitForAny(tacheControle, tacheMoteur, tacheCarburant, parallel.waitForAny(tacheControle, tacheMoteur, tacheCarburant,
tacheTypewriter, tacheIhm, tacheClavier) tachePosition, tacheTypewriter, tacheIhm, tacheClavier)

View file

@ -27,10 +27,12 @@ end
function Calibration.gimbal(conf, etat, materiel, journal, Etat, ui) function Calibration.gimbal(conf, etat, materiel, journal, Etat, ui)
local seuil = conf.CALIB.IMPULSION_SEUIL local seuil = conf.CALIB.IMPULSION_SEUIL
ui.progres(0, "mise a plat")
ui.inviter("Drone a plat, touchez l'ecran") ui.inviter("Drone a plat, touchez l'ecran")
ui.attendreToucher() ui.attendreToucher()
local x0, z0 = materiel.lireAnglesBruts() local x0, z0 = materiel.lireAnglesBruts()
ui.progres(0.25, "nez bas")
ui.inviter("Inclinez NEZ vers le BAS, touchez") ui.inviter("Inclinez NEZ vers le BAS, touchez")
ui.attendreToucher() ui.attendreToucher()
local x1, z1 = materiel.lireAnglesBruts() local x1, z1 = materiel.lireAnglesBruts()
@ -45,10 +47,12 @@ function Calibration.gimbal(conf, etat, materiel, journal, Etat, ui)
-- convention: nez bas => tangage normalise NEGATIF -- convention: nez bas => tangage normalise NEGATIF
local signeTangage = (deltaT < 0) and 1 or -1 local signeTangage = (deltaT < 0) and 1 or -1
ui.progres(0.5, "retour a plat")
ui.inviter("Revenez a plat, touchez") ui.inviter("Revenez a plat, touchez")
ui.attendreToucher() ui.attendreToucher()
x0, z0 = materiel.lireAnglesBruts() x0, z0 = materiel.lireAnglesBruts()
ui.progres(0.75, "penche a gauche")
ui.inviter("Penchez a GAUCHE, touchez") ui.inviter("Penchez a GAUCHE, touchez")
ui.attendreToucher() ui.attendreToucher()
x1, z1 = materiel.lireAnglesBruts() x1, z1 = materiel.lireAnglesBruts()
@ -69,6 +73,7 @@ function Calibration.gimbal(conf, etat, materiel, journal, Etat, ui)
Etat.sauver(etat) Etat.sauver(etat)
journal.info(("calib gimbal: tangage=angle%d(x%+d) roulis=angle%d(x%+d)") journal.info(("calib gimbal: tangage=angle%d(x%+d) roulis=angle%d(x%+d)")
:format(indexTangage, signeTangage, indexRoulis, signeRoulis)) :format(indexTangage, signeTangage, indexRoulis, signeRoulis))
ui.progres(1, "terminee")
ui.inviter("Gimbal calibre, remettez a plat") ui.inviter("Gimbal calibre, remettez a plat")
return true return true
end end
@ -105,7 +110,8 @@ function Calibration.joystick(conf, etat, materiel, journal, Etat, ui)
end end
local exclues = {} 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()) ui.inviter("Joystick a fond: " .. direction:upper())
local face = nil local face = nil
while not face do while not face do
@ -121,6 +127,7 @@ function Calibration.joystick(conf, etat, materiel, journal, Etat, ui)
etat.joystick = mapping etat.joystick = mapping
Etat.sauver(etat) Etat.sauver(etat)
ui.progres(1, "terminee")
ui.inviter("Joystick calibre") ui.inviter("Joystick calibre")
journal.info("calib joystick: terminee") journal.info("calib joystick: terminee")
return true 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", local symboles = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"valider", "effacer" } "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()) ui.inviter("Typewriter: appuyez sur " .. symbole:upper())
local code = nil local code = nil
while not code do while not code do
@ -161,6 +169,7 @@ function Calibration.typewriter(conf, etat, materiel, journal, Etat, ui)
etat.typewriter = mapping etat.typewriter = mapping
Etat.sauver(etat) Etat.sauver(etat)
ui.progres(1, "terminee")
ui.inviter("Typewriter calibre") ui.inviter("Typewriter calibre")
journal.info("calib typewriter: terminee") journal.info("calib typewriter: terminee")
return true return true
@ -181,20 +190,51 @@ function Calibration.rsc(conf, etat, materiel, journal, Etat, ui)
materiel.toutArreter() materiel.toutArreter()
local C = conf.CALIB local C = conf.CALIB
local nonAffectes = {} -- RECALIBRATION COMPLETE: on repart de zero (recablage possible)
for nom in pairs(materiel.rsc) do local anciensRoles = etat.roles
if not etat.roles[nom] then table.insert(nonAffectes, nom) end etat.roles = {}
end materiel.rafraichirAffectations()
table.sort(nonAffectes)
if #nonAffectes == 0 then local noms = {}
journal.info("calib rsc: tous les RSC sont deja affectes") 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 return true
end 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 = {} 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) journal.info("calib rsc: impulsion sur " .. nom)
ui.inviter("Impulsion: " .. nom)
local p = materiel.rsc[nom] local p = materiel.rsc[nom]
local t0, r0 = materiel.lireAssiette() local t0, r0 = materiel.lireAssiette()
local vitesse, dt_, dr = 0, 0, 0 local vitesse, dt_, dr = 0, 0, 0
@ -211,20 +251,26 @@ function Calibration.rsc(conf, etat, materiel, journal, Etat, ui)
end end
end end
p.setTargetSpeed(0) p.setTargetSpeed(0)
sleep(1.0)
if math.abs(dt_) >= C.IMPULSION_SEUIL if math.abs(dt_) >= C.IMPULSION_SEUIL
and math.abs(dr) >= C.IMPULSION_SEUIL then and math.abs(dr) >= C.IMPULSION_SEUIL then
-- assiette normalisee: tangage>0 = nez haut => helice a l'AVANT -- assiette normalisee: tangage>0 = nez haut => helice a l'AVANT
-- roulis>0 = penche a droite => helice a GAUCHE -- roulis>0 = penche a droite => helice a GAUCHE
local role = ((dr > 0) and "l" or "r") .. ((dt_ > 0) and "f" or "b") 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 etat.roles[nom] = role
ui.rscViolet(role)
journal.info(" -> helice " .. role) journal.info(" -> helice " .. role)
else else
table.insert(propulseurs, nom) table.insert(propulseurs, nom)
journal.info(" -> pas d'effet d'assiette: propulseur") journal.info(" -> pas d'effet d'assiette: propulseur")
end end
end end
ui.rscViolet(nil)
if #propulseurs == 2 then if #propulseurs == 2 then
etat.roles[propulseurs[1]] = "prop_l" 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: " journal.alerte("calib rsc: prop_l/prop_r affectes ARBITRAIREMENT: "
.. "si un virage gauche part a droite, les echanger dans drone.etat") .. "si un virage gauche part a droite, les echanger dans drone.etat")
elseif #propulseurs > 0 then elseif #propulseurs > 0 then
journal.alerte(("calib rsc: %d propulseur(s), 2 attendus") return abandonner(("%d propulseur(s) detecte(s), 2 attendus")
:format(#propulseurs)) :format(#propulseurs))
end end
Etat.sauver(etat) Etat.sauver(etat)
journal.info("calib rsc: terminee (relancer drone pour recharger)") materiel.rafraichirAffectations() -- prise d'effet immediate
ui.inviter("RSC calibres: relancer drone") ui.progres(1, "terminee")
journal.info("calib rsc: terminee")
return true return true
end 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 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...") ui.inviter("Calib capteurs: montee...")
local capteur, moyenne = mesurerPendant(mouvements.monter, {}) local capteur, moyenne = mesurerPendant(mouvements.monter, {})
etat.velocite[capteur] = { axe = "vertical", signe = signeDe(moyenne) } etat.velocite[capteur] = { axe = "vertical", signe = signeDe(moyenne) }
journal.info((" vertical: %s (x%+d)"):format(capteur, signeDe(moyenne))) journal.info((" vertical: %s (x%+d)"):format(capteur, signeDe(moyenne)))
local exclus = { [capteur] = true } local exclus = { [capteur] = true }
ui.progres(0.33, "avancee...")
ui.inviter("Calib capteurs: avancee...") ui.inviter("Calib capteurs: avancee...")
capteur, moyenne = mesurerPendant(mouvements.avancer, exclus) capteur, moyenne = mesurerPendant(mouvements.avancer, exclus)
etat.velocite[capteur] = { axe = "avant", signe = signeDe(moyenne) } etat.velocite[capteur] = { axe = "avant", signe = signeDe(moyenne) }
journal.info((" avant: %s (x%+d)"):format(capteur, signeDe(moyenne))) journal.info((" avant: %s (x%+d)"):format(capteur, signeDe(moyenne)))
exclus[capteur] = true exclus[capteur] = true
ui.progres(0.66, "translation gauche...")
ui.inviter("Calib capteurs: translation gauche...") ui.inviter("Calib capteurs: translation gauche...")
capteur, moyenne = mesurerPendant(mouvements.gauche, exclus) capteur, moyenne = mesurerPendant(mouvements.gauche, exclus)
etat.velocite[capteur] = { axe = "lateral", signe = signeDe(moyenne) } etat.velocite[capteur] = { axe = "lateral", signe = signeDe(moyenne) }
journal.info((" lateral: %s (x%+d)"):format(capteur, signeDe(moyenne))) journal.info((" lateral: %s (x%+d)"):format(capteur, signeDe(moyenne)))
Etat.sauver(etat) Etat.sauver(etat)
ui.inviter("Capteurs calibres: relancer drone") materiel.rafraichirAffectations()
journal.info("calib capteurs: terminee (relancer drone pour recharger)") ui.progres(1, "terminee")
ui.inviter("Capteurs calibres")
journal.info("calib capteurs: terminee")
return true return true
end end
@ -313,7 +369,7 @@ end
-- PID D'ALTITUDE (en vol stationnaire, marge d'altitude requise) -- PID D'ALTITUDE (en vol stationnaire, marge d'altitude requise)
-------------------------------------------------------------------- --------------------------------------------------------------------
function Calibration.pid(conf, etat, materiel, pilotage, journal, Etat, function Calibration.pid(conf, etat, materiel, pilotage, journal, Etat,
empreinte) empreinte, ui)
local C = conf.CALIB local C = conf.CALIB
local base = materiel.lireAltitude() local base = materiel.lireAltitude()
local gains = pilotage.gains() local gains = pilotage.gains()
@ -363,6 +419,9 @@ function Calibration.pid(conf, etat, materiel, pilotage, journal, Etat,
local meilleurs, meilleurTemps = nil, math.huge local meilleurs, meilleurTemps = nil, math.huge
for iter = 1, C.MAX_ITER do 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") 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)) :format(dir.nom, iter, C.MAX_ITER, g.kp, g.ki, g.kd))
pilotage.reglerGains(gains) 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 if math.abs(alt - base) <= C.BANDE then break end
sleep(dt) sleep(dt)
end end
ui.progres(1, "terminee")
journal.info("calib pid: terminee") journal.info("calib pid: terminee")
return true return true
end end

View file

@ -89,6 +89,8 @@ return {
STRESS_BAS = 0.60, -- en deca: reduire le moteur (economie) STRESS_BAS = 0.60, -- en deca: reduire le moteur (economie)
SURCHARGE = 0.98, -- au dela: reduire les propulseurs + alerte SURCHARGE = 0.98, -- au dela: reduire les propulseurs + alerte
PERIODE = 1.0, -- periode de l'asservissement (s) PERIODE = 1.0, -- periode de l'asservissement (s)
BOOST = 4.0, -- pre-boost a pleine puissance lors des
-- changements de regime exigeants (s)
}, },
------------------------------------------------------------------ ------------------------------------------------------------------

View file

@ -1,43 +1,80 @@
-------------------------------------------------------------------- --------------------------------------------------------------------
-- lib/ihm.lua : interfaces homme-machine. -- lib/ihm.lua : interfaces homme-machine.
-- Display Link "conduite" : altitudes, vitesse, invites, alertes -- Moniteur tactile : navigation par onglets (MODE / CALIB /
-- Display Link "carburant" : reserve + autonomie estimee (minutes) -- JOURNAL), echelle de texte auto (la plus grande qui loge le
-- Advanced monitor tactile : modes (verrouilles si calibration -- contenu), pages adaptees au mode, art du quadricoptere.
-- manquante), consigne, calibrations, informations -- Display Link conduite : altitudes / vitesse / messages longs
-- Typewriter : la saisie est geree dans drone.lua (scrutation) -- defiles par segments de mots entiers.
-- Display Link carburant : % + autonomie.
-------------------------------------------------------------------- --------------------------------------------------------------------
local Ihm = {} 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) function Ihm.nouveau(conf, materiel, journal)
local ihm = {} local ihm = {}
local moniteur = materiel.moniteur local moniteur = materiel.moniteur
moniteur.setTextScale(0.5)
local zones = {} 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) function ihm.message(texte, duree)
messageConduite = texte if texte == "" then segments = nil return end
messageFin = os.clock() + (duree or 5) -- 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 end
------------------------------------------------------------------
-- ECRAN DE CONDUITE
------------------------------------------------------------------
-- ctx: { mode, consigneY, saisie } -- ctx: { mode, consigneY, saisie }
function ihm.rafraichirConduite(ctx) function ihm.rafraichirConduite(ctx)
local e = materiel.ecranConduite local e = materiel.ecranConduite
e.clear() e.clear()
e.setCursorPos(1, 1) e.setCursorPos(1, 1)
if messageConduite and os.clock() < messageFin then local seg = segmentCourant()
e.write(messageConduite:sub(1, 22)) if seg then
e.write(seg:sub(1, 22))
elseif ctx.saisie and #ctx.saisie > 0 then 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" elseif ctx.mode == "vol" or ctx.mode == "drone"
or ctx.mode == "atterrissage" or ctx.mode == "auto" or ctx.mode == "atterrissage" or ctx.mode == "auto" then
or ctx.mode == "position" then
e.write(("ALT %.1f / %.0f"):format( e.write(("ALT %.1f / %.0f"):format(
materiel.lireAltitude(), ctx.consigneY or 0)) materiel.lireAltitude(), ctx.consigneY or 0))
else else
@ -48,10 +85,6 @@ function Ihm.nouveau(conf, materiel, journal)
e.update() e.update()
end end
------------------------------------------------------------------
-- ECRAN CARBURANT (peut etre tres etroit: format compact)
------------------------------------------------------------------
-- pct 0..1 ou nil ; minutes ou nil
function ihm.rafraichirCarburant(pct, minutes) function ihm.rafraichirCarburant(pct, minutes)
local e = materiel.ecranCarburant local e = materiel.ecranCarburant
local largeur = e.getSize() local largeur = e.getSize()
@ -62,23 +95,22 @@ function Ihm.nouveau(conf, materiel, journal)
else else
e.write(("%d%%"):format(math.floor(pct * 100 + 0.5)):sub(1, largeur)) e.write(("%d%%"):format(math.floor(pct * 100 + 0.5)):sub(1, largeur))
e.setCursorPos(1, 2) e.setCursorPos(1, 2)
if minutes then e.write((minutes and ("%dm"):format(math.floor(minutes)) or "--m")
e.write(("%dm"):format(math.floor(minutes)):sub(1, largeur)) :sub(1, largeur))
else
e.write(("--m"):sub(1, largeur))
end
end end
e.update() e.update()
end 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.setCursorPos(x, y)
moniteur.setBackgroundColor(couleur or colors.gray) moniteur.setBackgroundColor(couleur or colors.gray)
moniteur.setTextColor(couleurTexte or colors.white)
moniteur.write(" " .. texte .. " ") moniteur.write(" " .. texte .. " ")
moniteur.setBackgroundColor(colors.black) moniteur.setBackgroundColor(colors.black)
moniteur.setTextColor(colors.white)
table.insert(zones, { table.insert(zones, {
x1 = x, y1 = y, x2 = x + #texte + 1, y2 = y, action = action, 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) moniteur.setTextColor(colors.white)
end end
-- ctx: { mode, consigneY, stress, position, carburantPct, minutes, ------------------------------------------------------------------
-- moteurOk, verrous = { mode -> raison|nil }, points = n } -- ART DU QUADRICOPTERE
function ihm.rafraichirMoniteur(ctx) -- rectangle, 4 cercles aux coins (helices, rpm a cote), rpm des
zones = {} -- propulseurs sur les cotes; violet = RSC en cours de calibration
moniteur.setBackgroundColor(colors.black) ------------------------------------------------------------------
moniteur.clear() 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 function rpmDe(role)
local modes = { "off", "inactif", "stationnement", "vol", "drone", local s = materiel.rpm[role]
"atterrissage", "auto", "position" } 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 local x, y = 1, 2
for _, m in ipairs(modes) do for _, m in ipairs(modes) do
local verrou = ctx.verrous[m] local verrou = ctx.verrous[m]
local couleur = colors.gray local couleur = colors.gray
if m == ctx.mode then couleur = colors.green if m == ctx.mode then couleur = colors.green
elseif verrou then couleur = colors.red end 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 }, bouton(x, y, m, { type = "mode", valeur = m, verrou = verrou },
couleur) couleur)
x = x + #m + 3 x = x + #m + 3
end end
y = y + 2
texte(1, y + 2, ("== CONSIGNE == %.0f"):format(ctx.consigneY or 0)) local mode = ctx.mode
bouton(1, y + 3, "-10", { type = "delta", valeur = -10 }) y = blocCommun(y, ctx)
bouton(7, y + 3, "-1", { type = "delta", valeur = -1 }) if mode ~= "off" then y = blocStress(y, ctx) end
bouton(12, y + 3, "+1", { type = "delta", valeur = 1 }) y = y + 1
bouton(17, y + 3, "+10", { type = "delta", valeur = 10 })
texte(1, y + 5, "== CALIBRATIONS ==") if mode == "stationnement" or mode == "vol" or mode == "drone"
local cx = 1 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", for _, c in ipairs({ "gimbal", "joystick", "typew", "rsc",
"capteurs", "pid" }) do "capteurs", "pid" }) do
bouton(cx, y + 6, c, { type = "calib", valeur = c }) local statut = ctx.calibStatuts[c]
cx = cx + #c + 3 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
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 end
local ly = y + 8 ------------------------------------------------------------------
texte(1, ly, "== INFOS ==") -- PAGE JOURNAL
local t, r = materiel.lireAssiette() ------------------------------------------------------------------
texte(1, ly + 1, ("assiette T %+.2f R %+.2f%s"):format(t, r, local function dessinerPageJournal()
ctx.gimbalOk and "" or " (NON CALIBRE)")) local lignes = journal.recents(H - 2)
texte(1, ly + 2, ("sol %.1f (%s%s)"):format( for i, ligne in ipairs(lignes) do
materiel.lireDistanceSol(), texte(1, 1 + i, ligne:sub(1, L),
materiel.auSol() and "au sol" or "en vol", ligne:find("ERREUR") and colors.red
materiel.solPosable() and "" or ", NON POSABLE")) or ligne:find("ALERTE") and colors.orange or colors.white)
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)
end end
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) function ihm.traiterToucher(tx, ty)
for _, z in ipairs(zones) do for _, z in ipairs(zones) do
if tx >= z.x1 and tx <= z.x2 and ty >= z.y1 and ty <= z.y2 then 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 return z.action
end end
end end
return nil return nil
end end
-- attend un toucher du moniteur (utilise par les calibrations) function ihm.reglerPage(p) page = p end
function ihm.attendreToucher() function ihm.attendreToucher()
while true do while true do
local ev = os.pullEvent("monitor_touch") local ev = os.pullEvent("monitor_touch")

View file

@ -4,6 +4,20 @@
local Journal = {} local Journal = {}
local FICHIER, MAX_TAILLE = "drone.log", 64 * 1024 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) local function horodater(texte)
return ("[%s] %s"):format(textutils.formatTime(os.time("local"), true), texte) return ("[%s] %s"):format(textutils.formatTime(os.time("local"), true), texte)
@ -25,18 +39,21 @@ function Journal.info(texte)
local ligne = horodater(texte) local ligne = horodater(texte)
print(ligne) print(ligne)
ecrireFichier(ligne) ecrireFichier(ligne)
memoriser(ligne)
end end
function Journal.erreur(texte) function Journal.erreur(texte)
local ligne = horodater("ERREUR " .. texte) local ligne = horodater("ERREUR " .. texte)
printError(ligne) printError(ligne)
ecrireFichier(ligne) ecrireFichier(ligne)
memoriser(ligne)
end end
function Journal.alerte(texte) function Journal.alerte(texte)
local ligne = horodater("ALERTE " .. texte) local ligne = horodater("ALERTE " .. texte)
printError(ligne) printError(ligne)
ecrireFichier(ligne) ecrireFichier(ligne)
memoriser(ligne)
end end
return Journal return Journal

View file

@ -25,6 +25,9 @@ end
function Materiel.initialiser(conf, etat, journal) function Materiel.initialiser(conf, etat, journal)
local m = {} local m = {}
m.rpm = {} -- role -> { v, tChange } (pour l'affichage)
local posCache = nil, nil
local posDate = 0
m.altitude = trouver("altitude_sensor", true) m.altitude = trouver("altitude_sensor", true)
m.gimbal = trouver("gimbal_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.ecranConduite = ecran(conf.ECRANS.conduite, "conduite")
m.ecranCarburant = ecran(conf.ECRANS.carburant, "carburant") m.ecranCarburant = ecran(conf.ECRANS.carburant, "carburant")
-- rotation speed controllers -- rotation speed controllers et velocity sensors: les affectations
m.rsc, m.rscParRole = {}, {} -- (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 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) m.rsc[nom] = peripheral.wrap(nom)
local role = etat.roles[nom] elseif type_ == "velocity_sensor" then
if role then m.rscParRole[role] = m.rsc[nom] end m.velocite[nom] = peripheral.wrap(nom)
end end
end end
-- velocity sensors function m.rafraichirAffectations()
m.velocite, m.veloParAxe = {}, {} m.rscParRole, m.veloParAxe = {}, {}
for _, nom in ipairs(peripheral.getNames()) do for nom, p in pairs(m.rsc) do
if peripheral.getType(nom) == "velocity_sensor" then local role = etat.roles[nom]
m.velocite[nom] = peripheral.wrap(nom) if role then m.rscParRole[role] = p end
end
for nom, p in pairs(m.velocite) do
local aff = etat.velocite[nom] local aff = etat.velocite[nom]
if aff then if aff then m.veloParAxe[aff.axe] = { p = p, signe = aff.signe } end
m.veloParAxe[aff.axe] = { p = m.velocite[nom], signe = aff.signe }
end
end end
end end
m.rafraichirAffectations()
-- cuves de carburant (create:fluid_tank; exclut le moteur diesel, -- cuves de carburant (create:fluid_tank; exclut le moteur diesel,
-- qui est fluid_storage mais pas fluid_tank) -- qui est fluid_storage mais pas fluid_tank)
@ -177,6 +184,12 @@ function Materiel.initialiser(conf, etat, journal)
return m.stresso.getStress() / cap return m.stresso.getStress() / cap
end 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) -- capacite > 0 <=> le moteur tourne (verifie: capacite 0 a l'arret)
function m.moteurTourne() function m.moteurTourne()
if not m.stresso then return true end -- indetectable: on suppose oui 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 return quantite, capacite
end end
-- position GPS corrigee des offsets, ou nil -- ACTUALISATION GPS (BLOQUANTE, jusqu'a 2 s): a appeler depuis une
function m.lirePosition() -- tache dediee uniquement
function m.actualiserPosition()
local x, y, z = gps.locate(2) local x, y, z = gps.locate(2)
if not x then return nil end if x then
return x + conf.OFFSET_X, y + conf.OFFSET_Y, z + conf.OFFSET_Z 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 end
------------------------------------------------------------------ ------------------------------------------------------------------
@ -214,11 +237,20 @@ function Materiel.initialiser(conf, etat, journal)
function m.reglerRsc(role, vitesse) function m.reglerRsc(role, vitesse)
local p = m.rscParRole[role] 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 end
function m.toutArreter() function m.toutArreter()
for _, p in pairs(m.rsc) do p.setTargetSpeed(0) end 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 end
------------------------------------------------------------------ ------------------------------------------------------------------