Update Programme
This commit is contained in:
parent
6032da4967
commit
7298c8642d
7 changed files with 643 additions and 191 deletions
58
README.md
58
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 <x> <z>`. Decollage a Y_VOL,
|
||||
ralliement en avancant (cap estime par GPS), atterrissage sur place
|
||||
- `position` : au clavier: `position <x> <y> <z>`. Rallie le point;
|
||||
la descente est bloquee si le sol est detecte
|
||||
- `auto` : cible a 2 coordonnees (`auto <x> <z>` 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 <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
|
||||
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
|
||||
|
||||
|
|
|
|||
244
drone.lua
244
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 <x> <z> | position <x> <y> <z> | pt ... | rtb")
|
||||
print("auto <x> <z> | auto <x> <y> <z> | 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
|
|
|||
347
lib/ihm.lua
347
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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue