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,
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
View file

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

View file

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

View file

@ -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)
},
------------------------------------------------------------------

View file

@ -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")

View file

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

View file

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